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

Compare commits

...

37 Commits

Author SHA1 Message Date
carm 251dd208af feat(sql): Support SQL sources 2025-03-03 19:00:28 +08:00
carm afd1bbfc0f feat(sql): Support SQL sources 2025-03-03 18:52:41 +08:00
carm a699f6c164 feat(sql): Support SQL sources 2025-03-03 18:51:21 +08:00
carm 844cbfab53 feat(sql): Try to implement sql source 2025-02-27 13:32:12 +08:00
carm f74d5d29f9 feat(mongo): Finished source for MongoDB #105 2025-02-27 00:46:44 +08:00
carm 6f28abebb9 feat(section): Add #path and #fullPath for sections 2025-02-25 00:04:24 +08:00
LSeng 842cd78ce3 feat(section): Implement createSection() for ShadedSection 2025-02-22 23:56:45 +08:00
carm 11f1f36a15 docs: Change logo file 2025-02-22 10:13:54 +08:00
renovate[bot] ee9f29ba30 chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.0 2025-02-22 09:57:51 +08:00
carm f5bccdaad5 chore: Upgraded to 4.0.6 2025-02-22 09:57:24 +08:00
carm f5f70ff69b chore(source): Add more pre implemented functions 2025-02-22 09:56:59 +08:00
carm 3e221740bc feat(section): Implement shaded sections 2025-02-22 09:55:28 +08:00
LSeng 96d09be977 feat(section): support ShadedSection 2025-02-21 20:48:37 +08:00
carm 5d7c946db5 feat(section): Implement more sections 2025-02-21 18:45:15 +08:00
carm 9e008ff4cd feat(section): Add shaded section 2025-02-21 17:31:05 +08:00
carm df19da170b feat(section): Add #contains and #containsValue methods 2025-02-21 17:15:16 +08:00
carm 3473ef2247 feat: Implement more sections functions 2025-02-21 16:17:12 +08:00
carm d543530305 feat: Implement more sections functions 2025-02-21 16:09:58 +08:00
carm d81855697c feat: Implement more sections functions 2025-02-21 11:27:03 +08:00
carm 5c16e98f30 feat(section): Implement more sections 2025-02-21 03:20:11 +08:00
carm 3f1ffadeff feat(section): Implement more sections 2025-02-21 03:07:43 +08:00
carm d8191b7c6d feat(section): Add original method 2025-02-21 02:00:36 +08:00
carm ba66220f92 docs(sponsor): Add sponsors banner 2025-02-21 01:00:27 +08:00
carm 730d6d7e9c docs(sponsor): Add sponsors banner 2025-02-21 00:58:32 +08:00
carm 4523190cb0 feat: Upgraded to 4.0.5 2025-02-20 16:50:07 +08:00
carm 2a49e2ee6b docs: Fixed quantity usage 2025-02-20 16:49:36 +08:00
carm 77b223b2cb feat: Add stream functions 2025-02-20 16:38:49 +08:00
carm 8e7ac263e7 feat: Split MapSection and MemorySection 2025-02-20 03:01:45 +08:00
carm 80f03ec501 feat: Split MapSection and MemorySection 2025-02-20 02:52:19 +08:00
carm 1c002ae535 feat: Split MapSection and MemorySection 2025-02-20 02:50:00 +08:00
carm a4659c5c9f docs: Englished documents 2025-02-20 00:11:06 +08:00
carm 1c27c2f881 fix: Fixed the missing implements of #remove 2025-02-19 00:57:17 +08:00
carm 476cb79e2f feat: Support standard "pre-load" option 2025-02-18 21:47:58 +08:00
carm bf716b06ae docs: Add more javadocs 2025-02-18 17:33:28 +08:00
carm 00170e6d77 feat: Simplify the ConfigureSection functions to support more different usages. 2025-02-18 17:08:12 +08:00
carm 1bac201427 chore: Add more limitations for inline comments 2025-02-18 14:12:28 +08:00
carm cd77639df5 docs: Add examples 2025-02-17 18:37:16 +08:00
61 changed files with 2503 additions and 567 deletions
+55 -26
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);
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() @HeaderComments("Server configurations") // Header comment
.parseValue(UUID::fromString).serializeValue(UUID::toString) ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
interface INFO extends Configuration { @HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
@HeaderComment("Configure your name!") // Header comment @ConfigPath("info") // Custom path
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker"); interface INFO extends Configuration {
@ConfigPath("year") // Custom path @HeaderComments("Configure your name!") // Header comment
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24); ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("how-old-are-you") // Custom path
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() + ")!");
} }
} }
@@ -92,16 +109,22 @@ public class Sample {
```yaml ```yaml
# Configurations for sample # Configurations for sample
enabled: true # Enabled? enabled: true #Enabled?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids: uuids:
- 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info: info:
# Configure your name! # Configure your name!
name: Joker name: Joker
year: 24 how-old-are-you: 24
``` ```
### Dependencies ### Dependencies
@@ -159,10 +182,10 @@ info:
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments. --> <!-- JSON file-based implementation, compatible with all Java environments. -->
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId> <artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version> <version>[LATEST RELEASE]</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -205,8 +228,8 @@ dependencies {
// YAML file-based implementation, compatible with all Java environments. // YAML file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
// JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments. // JSON file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
} }
``` ```
@@ -220,7 +243,8 @@ dependencies {
EasyConfiguration for MineCraft! EasyConfiguration for MineCraft!
Easily manage configurations on MineCraft-related server platforms. Easily manage configurations on MineCraft-related server platforms.
Currently supports BungeeCord, Bukkit (Spigot) servers, with more platforms to be supported soon. Currently, it supports BungeeCord, Velocity, Bukkit (Spigot) servers,
with more platforms to be supported soon.
## Support and Donation ## Support and Donation
@@ -232,6 +256,11 @@ Many thanks to Jetbrains for kindly providing a license for us to work on this a
[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/EasyConfiguration) [![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/EasyConfiguration)
Many thanks to [ArtformGames](https://github.com/ArtformGames) for their
strong support and active contribution to this project!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## Open Source License ## Open Source License
This project's source code is licensed under This project's source code is licensed under
+46 -19
View File
@@ -32,26 +32,35 @@ README LANGUAGES [ [English](README.md) | [**中文**](README_CN.md) ]
## 开发 ## 开发
详细开发介绍请 [点击这里](.doc/README.md) , JavaDoc(最新Release) 请 [点击这里](https://CarmJos.github.io/EasyConfiguration) 。 详细开发介绍请 [点击这里](.doc/README.md) , JavaDoc(最新Release)
请 [点击这里](https://CarmJos.github.io/EasyConfiguration) 。
### 示例代码 ### 示例代码
为快速的展示该项目的适用性,这里有几个实际演示: 为快速的展示该项目的适用性,这里有几个实际演示:
- [数据库配置文件实例](demo/src/main/java/cc/carm/lib/configuration/demo/DatabaseConfiguration.java) - [数据库配置文件实例](demo/src/main/java/cc/carm/lib/configuration/demo/DatabaseConfiguration.java)
- [全种类配置实例类演示](demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java) - [全种类配置实例类演示](demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java)
您可以 [点击这里](demo/src/main/java/cc/carm/lib/configuration/demo) 直接查看现有的代码演示,更多复杂情况演示详见 [开发介绍](.doc/README.md) 。 您可以 [点击这里](demo/src/main/java/cc/carm/lib/configuration/demo)
直接查看现有的代码演示,更多复杂情况演示详见 [开发介绍](.doc/README.md) 。
```java ```java
@HeaderComment("Configurations for sample")
interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // 行内注释 @ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // 行后注释
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true); ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@HeaderComments("Server configurations") // 头部注释
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parseValue(UUID::fromString).serializeValue(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001") UUID.fromString("00000000-0000-0000-0000-000000000001")
@@ -59,26 +68,34 @@ interface SampleConfig extends Configuration {
interface INFO extends Configuration { interface INFO extends Configuration {
@HeaderComment("Configure your name!") // 头部注释 @HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker"); ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // 自定义配置路径,若不定义,则按照默认规则生成 @ConfigPath("how-old-are-you") // 自定义路径
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24); ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
} }
} }
``` ```
```java ```java
public class Sample { public class Sample {
public static void main(String[] args) { public static void main(String[] args) {
// 1. 生成一个 “Provider” 用于给配置类提供源配置的文件。 // 1. 生成一个 “holder” 用于给配置类提供源配置的文件。
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml"); ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
// 2. 通过 “Provider” 初始化配置类或配置实例。 .resourcePath("configs/sample.yml")
provider.initialize(SampleConfig.class); .indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. 通过 “holder” 初始化配置类或配置实例。
holder.initialize(SampleConfig.class);
// 3. 现在可以享受快捷方便的配置文件使用方式了~ // 3. 现在可以享受快捷方便的配置文件使用方式了~
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false); SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.getNotNull() + " !"); System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
// p.s. 在本示例里的更改未保存,因此启用值在下次运行中仍将为 true。
} }
} }
``` ```
@@ -86,16 +103,22 @@ public class Sample {
```yaml ```yaml
# Configurations for sample # Configurations for sample
enabled: true # Enabled? enabled: true #Enabled?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids: uuids:
- 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info: info:
# Configure your name! # Configure your name!
name: Joker name: Joker
year: 24 how-old-are-you: 24
``` ```
### 依赖方式 ### 依赖方式
@@ -164,7 +187,7 @@ info:
<!--需要注意的是,JSON不支持文件注释。--> <!--需要注意的是,JSON不支持文件注释。-->
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId> <artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version> <version>[LATEST RELEASE]</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -223,9 +246,9 @@ dependencies {
//基于YAML文件的实现版本,可用于全部Java环境。 //基于YAML文件的实现版本,可用于全部Java环境。
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
//基于JSON文件的实现版本,可用于全部Java环境。 //基于JSON文件的实现版本,可用于全部Java环境。
//需要注意的是,JSON不支持文件注释。 //需要注意的是,JSON不支持文件注释。
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]"
@@ -255,6 +278,10 @@ EasyConfiguration for MineCraft!
[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/EasyConfiguration) [![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/EasyConfiguration)
万分感谢来自 [ArtformGames](https://github.com/ArtformGames) 对本项目的大力支持与积极贡献!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## 开源协议 ## 开源协议
本项目源码采用 [GNU LESSER GENERAL PUBLIC LICENSE](https://www.gnu.org/licenses/lgpl-3.0.html) 开源协议。 本项目源码采用 [GNU LESSER GENERAL PUBLIC LICENSE](https://www.gnu.org/licenses/lgpl-3.0.html) 开源协议。
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.1</version> <version>4.0.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -157,7 +158,7 @@ public abstract class ValueType<T> {
ParameterizedType pt = (ParameterizedType) type; ParameterizedType pt = (ParameterizedType) type;
Type raw = pt.getRawType(); Type raw = pt.getRawType();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(raw.getTypeName()); sb.append(raw.getClass().getName());
sb.append('<'); sb.append('<');
Type[] args = pt.getActualTypeArguments(); Type[] args = pt.getActualTypeArguments();
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
@@ -166,6 +167,7 @@ public abstract class ValueType<T> {
} }
sb.append(args[i].getTypeName()); sb.append(args[i].getTypeName());
} }
sb.append('>');
return sb.toString(); return sb.toString();
} }
return type.getTypeName(); return type.getTypeName();
@@ -20,6 +20,13 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
/**
* ConfigurationFactory, used to create configuration holder.
*
* @param <SOURCE> The {@link ConfigureSource} type
* @param <HOLDER> The {@link ConfigurationHolder} type.
* @param <SELF> Self builder, for further implement support.
*/
public abstract class ConfigurationFactory< public abstract class ConfigurationFactory<
SOURCE extends ConfigureSource<?, ?, SOURCE>, SOURCE extends ConfigureSource<?, ?, SOURCE>,
HOLDER extends ConfigurationHolder<SOURCE>, HOLDER extends ConfigurationHolder<SOURCE>,
@@ -139,18 +146,39 @@ public abstract class ConfigurationFactory<
return self(); return self();
} }
/**
* Supply the base path generator for this configuration holder
*
* @param generator {@link PathGenerator}
* @return this
*/
public SELF pathGenerator(PathGenerator generator) { public SELF pathGenerator(PathGenerator generator) {
return initializer(loader -> { return initializer(loader -> {
loader.pathGenerator(generator); loader.pathGenerator(generator);
}); });
} }
/**
* Register a new annotation for metadata to the configuration loader
*
* @param annotation The {@link Annotation}
* @param metadata The {@link ConfigurationMetadata} type
* @param extractor The {@link Function} to extract the metadata from the annotation
* @param <M> The metadata type
* @param <A> The annotation type
* @return this
*/
public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation, public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<M> metadata, @NotNull ConfigurationMetadata<M> metadata,
@NotNull Function<A, M> extractor) { @NotNull Function<A, M> extractor) {
return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor)); return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor));
} }
/**
* Build the configuration holder.
*
* @return The configuration holder
*/
public abstract @NotNull HOLDER build(); public abstract @NotNull HOLDER build();
} }
@@ -6,8 +6,11 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer; import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata; import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder; import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest; import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -17,6 +20,8 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> { public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
@@ -50,6 +55,10 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options; return options;
} }
public <O> @NotNull O option(@NotNull ConfigurationOption<O> option) {
return options().get(option);
}
public @NotNull Map<String, ConfigurationMetaHolder> metadata() { public @NotNull Map<String, ConfigurationMetaHolder> metadata() {
return this.metadata; return this.metadata;
} }
@@ -61,14 +70,26 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) { public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) {
return extractMetadata(type, Objects::nonNull);
}
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type, @NotNull Predicate<M> filter) {
Map<String, M> metas = new LinkedHashMap<>(); Map<String, M> metas = new LinkedHashMap<>();
for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) { for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) {
M data = entry.getValue().get(type); M data = entry.getValue().get(type);
if (data != null) metas.put(entry.getKey(), data); if (filter.test(data)) metas.put(entry.getKey(), data);
} }
return Collections.unmodifiableMap(metas); return Collections.unmodifiableMap(metas);
} }
@NotNull
@UnmodifiableView
public Map<String, ConfigValue<?>> registeredValues() {
return extractMetadata(StandardMeta.VALUE);
}
public ValueAdapterRegistry adapters() { public ValueAdapterRegistry adapters() {
return this.adapters; return this.adapters;
} }
@@ -5,26 +5,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@FunctionalInterface @FunctionalInterface
public interface ConfigInitializeHandler<T> { public interface ConfigInitializeHandler<T, V> {
static <T> ConfigInitializeHandler<T> start() { static <T, V> ConfigInitializeHandler<T, V> start() {
return (provider, path, value) -> { return (provider, path, value, instace) -> {
}; };
} }
void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path, @NotNull T value) throws Exception; void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path,
@NotNull T value, @Nullable V instance) throws Exception;
default ConfigInitializeHandler<T> andThen(ConfigInitializeHandler<T> after) { default ConfigInitializeHandler<T, V> andThen(ConfigInitializeHandler<T, V> after) {
return (provider, path, value) -> { return (provider, path, value, instance) -> {
whenInitialize(provider, path, value); whenInitialize(provider, path, value, instance);
after.whenInitialize(provider, path, value); after.whenInitialize(provider, path, value, instance);
}; };
} }
default ConfigInitializeHandler<T> compose(ConfigInitializeHandler<T> before) { default ConfigInitializeHandler<T, V> compose(ConfigInitializeHandler<T, V> before) {
return (provider, path, value) -> { return (provider, path, value, instance) -> {
before.whenInitialize(provider, path, value); before.whenInitialize(provider, path, value, instance);
whenInitialize(provider, path, value); whenInitialize(provider, path, value, instance);
}; };
} }
@@ -21,18 +21,18 @@ import java.util.function.Function;
public class ConfigurationInitializer { public class ConfigurationInitializer {
protected @NotNull PathGenerator pathGenerator; protected @NotNull PathGenerator pathGenerator;
protected @NotNull ConfigInitializeHandler<Field> fieldInitializer; protected @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer; protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer;
public ConfigurationInitializer() { public ConfigurationInitializer() {
this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start()); this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start());
} }
public ConfigurationInitializer(@NotNull PathGenerator pathGenerator, public ConfigurationInitializer(@NotNull PathGenerator pathGenerator,
@NotNull ConfigInitializeHandler<Field> fieldInitializer, @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.pathGenerator = pathGenerator; this.pathGenerator = pathGenerator;
this.fieldInitializer = fieldInitializer; this.valueInitializer = valueInitializer;
this.classInitializer = classInitializer; this.classInitializer = classInitializer;
} }
@@ -44,34 +44,34 @@ public class ConfigurationInitializer {
return pathGenerator; return pathGenerator;
} }
public ConfigInitializeHandler<Field> fieldInitializer() { public ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer() {
return fieldInitializer; return valueInitializer;
} }
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) { public void fieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.fieldInitializer = fieldInitializer; this.valueInitializer = fieldInitializer;
} }
public ConfigInitializeHandler<Class<? extends Configuration>> classInitializer() { public ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer() {
return classInitializer; return classInitializer;
} }
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = classInitializer; this.classInitializer = classInitializer;
} }
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) { public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.fieldInitializer = this.fieldInitializer.andThen(fieldInitializer); this.valueInitializer = this.valueInitializer.andThen(fieldInitializer);
} }
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = this.classInitializer.andThen(classInitializer); this.classInitializer = this.classInitializer.andThen(classInitializer);
} }
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> { appendClassInitializer((holder, path, clazz, instance) -> {
A data = clazz.getAnnotation(annotation); A data = clazz.getAnnotation(annotation);
if (data == null) return; if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data)); holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -81,7 +81,7 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> { appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation); A data = field.getAnnotation(annotation);
if (data == null) return; if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data)); holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -108,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();
} }
@@ -123,7 +123,7 @@ public class ConfigurationInitializer {
@Nullable String parentPath, @Nullable Field configField) { @Nullable String parentPath, @Nullable Field configField) {
String path = getClassPath(holder, parentPath, root.getClass(), configField); String path = getClassPath(holder, parentPath, root.getClass(), configField);
try { try {
this.classInitializer.whenInitialize(holder, path, root.getClass()); this.classInitializer.whenInitialize(holder, path, root.getClass(), root);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -140,7 +140,7 @@ public class ConfigurationInitializer {
String path = getClassPath(holder, parentPath, clazz, configField); String path = getClassPath(holder, parentPath, clazz, configField);
try { try {
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz); this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz, configField);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -149,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);
@@ -169,14 +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. holder.metadata(path).set(StandardMeta.VALUE, value); // Mark the minimal config value unit.
if (holder.option(StandardOptions.SET_DEFAULTS)) {
value.setDefault(); // Set default value.
}
try { try {
this.fieldInitializer.whenInitialize(holder, path, field); this.valueInitializer.whenInitialize(holder, path, field, value);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
if (holder.options().get(StandardOptions.SET_DEFAULTS)) { if (holder.option(StandardOptions.PRELOAD)) {
value.setDefault(); value.get(); // Preload the value by calling #get method.
} }
} else if (source instanceof Configuration && object instanceof Configuration) { } else if (source instanceof Configuration && object instanceof Configuration) {
// 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。 // 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。
@@ -9,6 +9,12 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
/**
* Path generator for configuration.
* <p>
* Path generator is a utility class that helps to generate the path of the configuration.
* It can be used to generate the path of the field or class.
*/
public class PathGenerator { public class PathGenerator {
public static PathGenerator of() { public static PathGenerator of() {
@@ -80,7 +86,7 @@ public class PathGenerator {
} }
public static char pathSeparator(ConfigurationHolder<?> holder) { public static char pathSeparator(ConfigurationHolder<?> holder) {
return holder.options().get(StandardOptions.PATH_SEPARATOR); return holder.option(StandardOptions.PATH_SEPARATOR);
} }
/** /**
@@ -1,10 +1,12 @@
package cc.carm.lib.configuration.source.meta; package cc.carm.lib.configuration.source.meta;
import cc.carm.lib.configuration.value.ConfigValue;
public interface StandardMeta { public interface StandardMeta {
/** /**
* To mark the {@link cc.carm.lib.configuration.value.ConfigValue} as a minimal unit path. * To mark the {@link ConfigValue} instance of specific path.
*/ */
ConfigurationMetadata<Boolean> UNIT = ConfigurationMetadata.of(false); ConfigurationMetadata<ConfigValue<?>> VALUE = ConfigurationMetadata.of();
} }
@@ -6,32 +6,20 @@ import java.util.function.Supplier;
public class ConfigurationOption<V> { public class ConfigurationOption<V> {
@SuppressWarnings("unchecked")
public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) { public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) {
return of((Class<T>) defaultValue.getClass(), defaultValue); return new ConfigurationOption<>(defaultValue);
}
public static <T> ConfigurationOption<T> of(@NotNull Class<T> valueClazz, @NotNull T defaultValue) {
return new ConfigurationOption<>(valueClazz, defaultValue);
} }
public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) { public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) {
return of(defaultValue.get()); return of(defaultValue.get());
} }
private final @NotNull Class<V> valueClazz;
private @NotNull V defaultValue; private @NotNull V defaultValue;
public ConfigurationOption(@NotNull Class<V> valueClazz, @NotNull V defaultValue) { public ConfigurationOption(@NotNull V defaultValue) {
this.valueClazz = valueClazz;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }
@NotNull
public Class<V> valueClass() {
return this.valueClazz;
}
public @NotNull V defaults() { public @NotNull V defaults() {
return defaultValue; return defaultValue;
} }
@@ -23,12 +23,13 @@ public interface StandardOptions {
ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true); ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true);
/** /**
* Whether to pre parse the config values. * Whether to pre parse the config values,
* <br> if false, the values will be parsed when calling * may good to set true if you want to keep the config format.
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* <br> if true, the values will be parsed when * <br> if true, the values will be parsed when
* {@link ConfigurationHolder#initialize(Configuration)}. * {@link ConfigurationHolder#initialize(Configuration)}.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
*/ */
ConfigurationOption<Boolean> PRELOAD = of(false); ConfigurationOption<Boolean> PRELOAD = of(true);
} }
@@ -1,73 +1,441 @@
package cc.carm.lib.configuration.source.section; package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction; import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView; import org.jetbrains.annotations.UnmodifiableView;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
/**
* Represents a section of a configuration.
*
* @author Carm
* @since 4.0.0
*/
public interface ConfigureSection { public interface ConfigureSection {
@NotNull ConfigureSource<?, ?, ?> source(); /**
* Gets the parent section of this section.
* <p>
* For root sections, this will return null.
*
* @return Parent section, or null if this is a root section.
*/
@Contract(pure = true)
@Nullable ConfigureSection parent(); @Nullable ConfigureSection parent();
default char separator() { /**
return source().holder().options().get(StandardOptions.PATH_SEPARATOR); * Get the current section's path from {@link #parent()} of this section.
*
* @return The current path of this section, if {@link #isRoot()}, return empty string.
*/
@NotNull String path();
/**
* Get the full path of this section.
*
* @return The full path of this section, if {@link #isRoot()}, return empty string.
*/
default @NotNull String fullPath() {
if (parent() == null) return "";
return (parent().isRoot() ? "" : parent().fullPath() + pathSeparator()) + path();
} }
/**
* Get the path separator for the section.
*
* @return The path separator
*/
default char pathSeparator() {
return '.';
}
/**
* Gets if this section is a root section.
*
* @return True if this section is a root section, false otherwise.
*/
@Contract(pure = true)
default boolean isRoot() {
return parent() == null;
}
/**
* Gets if this section is empty.
*
* @return True if this section is empty, false otherwise.
*/
default boolean isEmpty() {
return getValues(false).isEmpty();
}
/**
* Gets the number of keys in this section.
*
* @return Number of keys in this section.
*/
default int size(boolean deep) {
return getKeys(deep).size();
}
/**
* Gets a set containing all keys in this section.
* <p>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigureSection}s (and their children paths).
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether to get a deep list.
* @return Set of keys contained within this Section.
*/
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
default Set<String> getKeys(boolean deep) { default Set<String> getKeys(boolean deep) {
return getValues(deep).keySet(); return getValues(deep).keySet();
} }
/**
* Gets a set containing all primary keys in this section.
*
* @return Set of keys contained within this Section.
*/
@NotNull
@UnmodifiableView
default Set<String> keys() {
return getKeys(false);
}
/**
* Gets a set containing all values in this section.
* <p>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigureSection}s (and their children paths).
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether to get a deep list.
* @return Map of data values contained within this Section.
*/
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
Map<String, Object> getValues(boolean deep); Map<String, Object> getValues(boolean deep);
/**
* Gets a set containing all key-values in this section.
*
* @return Map of data values contained within this Section.
* @see #getValues(boolean)
*/
@NotNull
@UnmodifiableView
default Map<String, Object> values() {
return getValues(false);
}
/**
* Create a stream of all values in this section.
*
* @return Stream of all values in this section.
*/
default Stream<Map.Entry<String, Object>> stream() {
return values().entrySet().stream();
}
/**
* Iterates over all keys in this section.
*
* @param action The action to apply to each key.
*/
default void forEach(@NotNull BiConsumer<String, Object> action) {
values().forEach(action);
}
/**
* Sets the value at the given path.
* <p>
* Null values will be kept, if you want to remove a value use {@link #remove(String)}
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to set the value at.
* @param value The value to set.
*/
void set(@NotNull String path, @Nullable Object value); void set(@NotNull String path, @Nullable Object value);
boolean contains(@NotNull String path); /**
* Removes the value at the given path.
* <p>
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to remove the value at.
*/
void remove(@NotNull String path);
/**
* Check if the given path is present.
* <p>
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to check.
* @return True if the value is present, false otherwise.
*/
default boolean contains(@NotNull String path) {
return getKeys(true).contains(path);
}
/**
* Check if the value of given path is present.
* <p>
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to check.
* @return True if the value is present, false otherwise.
*/
default boolean containsValue(@NotNull String path) {
return get(path) != null;
}
/**
* Predicate the value of given path is specific type.
*
* @param path The path to check.
* @param typeClass The type's class
* @param <T> The type to check.
* @return True if the value is present and is the correct type, false otherwise.
*/
default <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) { default <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) {
return typeClass.isInstance(get(path)); return typeClass.isInstance(get(path));
} }
/**
* Predicate the value of given path is a {@link List}.
*
* @param path The path to check.
* @return True if the value is present and is a list, false otherwise.
*/
default boolean isList(@NotNull String path) { default boolean isList(@NotNull String path) {
return isType(path, List.class); return isType(path, List.class);
} }
@Nullable List<?> getList(@NotNull String path); /**
* Get the value as a {@link List} from the specified path.
*
* @param path The path to get the {@link List}.
* @return The list if the path exists and is a list, otherwise null.
*/
default @Nullable List<?> getList(@NotNull String path) {
Object val = get(path);
return (val instanceof List<?>) ? (List<?>) val : null;
}
/**
* Predicate the value of given path is a {@link ConfigureSection}.
*
* @param path The path to check.
* @return True if the value is present and is a section, false otherwise.
*/
default boolean isSection(@NotNull String path) { default boolean isSection(@NotNull String path) {
return isType(path, ConfigureSection.class); return isType(path, ConfigureSection.class);
} }
/**
* Get the value as a {@link ConfigureSection} from the specified path.
*
* @param path The path to get the section.
* @return The section if the path exists and is a section, otherwise null.
*/
@Nullable @Nullable
ConfigureSection getSection(@NotNull String path); default ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
@Nullable Object get(@NotNull String path); return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
default @Nullable <T> T get(@NotNull String path, @NotNull Class<T> clazz) {
return get(path, null, clazz);
} }
default @Nullable <T> T get(@NotNull String path, @NotNull DataFunction<Object, T> parser) { /**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
* <p> This section will not be saved until {@link #set(String, Object)} is called.
* <p> If you want to create and use a section and set it to this section, use {@link #computeSection(String)}.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
@NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data);
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Consumer<Map<String, Object>> data) {
return createSection(path, () -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Supplier<Map<String, Object>> data) {
return createSection(path, data.get());
}
/**
* Creates a new empty {@link ConfigureSection}.
* <p> The {@link #parent()} of the new section will be this section.
*
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path) {
return createSection(path, new LinkedHashMap<>());
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path) {
return computeSection(path, new LinkedHashMap<>());
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Map<?, ?> data) {
ConfigureSection current = getSection(path);
if (current == null) {
current = createSection(path, data);
set(path, current);
}
return current;
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Consumer<Map<String, Object>> data) {
return computeSection(path, () -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Supplier<Map<String, Object>> data) {
return computeSection(path, data.get());
}
/**
* Get the origin value of the path.
*
* @param path The path to get the value from.
* @return The value at the path, or null if not found.
*/
@Nullable Object get(@NotNull String path);
/**
* Get the value of the path for specific type,
* if the path does not exist, return null.
*
* @param path The path to get the value from.
* @param type The type class of the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
default @Nullable <T> T get(@NotNull String path, @NotNull Class<T> type) {
return get(path, null, type);
}
/**
* Get the value of the path using a parser function,
* if the path does not exist, return NULL.
*
* @param path The path to get the value from.
* @param parser The function to parse the value
* @param <T> The type of the value
* @return The value at the path, or null if not found.
*/
default @Nullable <T> T get(@NotNull String path, @NotNull DataFunction<@Nullable Object, T> parser) {
return get(path, null, parser); return get(path, null, parser);
} }
/**
* Get the value of the path for specific type,
* if the path does not exist, return the default value.
*
* @param path The path to get the value from.
* @param defaults The default value to return if the path does not exist.
* @param clazz The type class of the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
@Contract("_,!null,_->!null") @Contract("_,!null,_->!null")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, @NotNull Class<T> clazz) { default @Nullable <T> T get(@NotNull String path, @Nullable T defaults, @NotNull Class<T> clazz) {
return get(path, defaultValue, DataFunction.castObject(clazz)); return get(path, defaults, DataFunction.castObject(clazz));
} }
/**
* Get the value of the path using a parser function,
* if the path does not exist, return the default value.
*
* @param path The path to get the value from.
* @param defaultValue The default value to return if the path does not exist.
* @param parser The function to parse the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
@Contract("_,!null,_->!null") @Contract("_,!null,_->!null")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue,
@NotNull DataFunction<Object, T> parser) { @NotNull DataFunction<Object, T> parser) {
@@ -82,125 +450,290 @@ public interface ConfigureSection {
return defaultValue; return defaultValue;
} }
/**
* Predicate the value of given path is a {@link Boolean}.
*
* @param path The path to check.
* @return True if the value is present and is a boolean, false otherwise.
*/
default boolean isBoolean(@NotNull String path) { default boolean isBoolean(@NotNull String path) {
return isType(path, Boolean.class); return isType(path, Boolean.class);
} }
/**
* Get the value as a {@link Boolean} from the specified path.
*
* @param path The path to get the boolean.
* @return The boolean if the path exists and is a boolean, otherwise false.
*/
default boolean getBoolean(@NotNull String path) { default boolean getBoolean(@NotNull String path) {
return getBoolean(path, false); return getBoolean(path, false);
} }
/**
* Get the value as a {@link Boolean} from the specified path.
*
* @param path The path to get the boolean.
* @param def The default value to return if the path does not exist.
* @return The boolean if the path exists and is a boolean, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) { default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) {
return get(path, def, DataFunction.booleanValue()); return get(path, def, DataFunction.booleanValue());
} }
/**
* Predicate the value of given path is a {@link Byte}.
*
* @param path The path to check.
* @return True if the value is present and is a byte, false otherwise.
*/
default @Nullable Boolean isByte(@NotNull String path) { default @Nullable Boolean isByte(@NotNull String path) {
return isType(path, Byte.class); return isType(path, Byte.class);
} }
/**
* Get the value as a {@link Byte} from the specified path.
*
* @param path The path to get the byte.
* @return The byte if the path exists and is a byte, otherwise 0.
*/
default @Nullable Byte getByte(@NotNull String path) { default @Nullable Byte getByte(@NotNull String path) {
return getByte(path, (byte) 0); return getByte(path, (byte) 0);
} }
/**
* Get the value as a {@link Byte} from the specified path.
*
* @param path The path to get the byte.
* @param def The default value to return if the path does not exist.
* @return The byte if the path exists and is a byte, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) { default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) {
return get(path, def, DataFunction.byteValue()); return get(path, def, DataFunction.byteValue());
} }
/**
* Predicate the value of given path is a {@link Short}.
*
* @param path The path to check.
* @return True if the value is present and is a short, false otherwise.
*/
default boolean isShort(@NotNull String path) { default boolean isShort(@NotNull String path) {
return isType(path, Short.class); return isType(path, Short.class);
} }
/**
* Get the value as a {@link Short} from the specified path.
*
* @param path The path to get the short.
* @return The short if the path exists and is a short, otherwise 0.
*/
default @Nullable Short getShort(@NotNull String path) { default @Nullable Short getShort(@NotNull String path) {
return getShort(path, (short) 0); return getShort(path, (short) 0);
} }
/**
* Get the value as a {@link Short} from the specified path.
*
* @param path The path to get the short.
* @param def The default value to return if the path does not exist.
* @return The short if the path exists and is a short, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Short getShort(@NotNull String path, @Nullable Short def) { default @Nullable Short getShort(@NotNull String path, @Nullable Short def) {
return get(path, def, DataFunction.shortValue()); return get(path, def, DataFunction.shortValue());
} }
/**
* Predicate the value of given path is a {@link Integer}.
*
* @param path The path to check.
* @return True if the value is present and is an int, false otherwise.
*/
default boolean isInt(@NotNull String path) { default boolean isInt(@NotNull String path) {
return isType(path, Integer.class); return isType(path, Integer.class);
} }
/**
* Get the value as a {@link Integer} from the specified path.
*
* @param path The path to get the int.
* @return The int if the path exists and is an int, otherwise 0.
*/
default @Nullable Integer getInt(@NotNull String path) { default @Nullable Integer getInt(@NotNull String path) {
return getInt(path, 0); return getInt(path, 0);
} }
/**
* Get the value as a {@link Integer} from the specified path.
*
* @param path The path to get the int.
* @param def The default value to return if the path does not exist.
* @return The int if the path exists and is an int, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) { default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
return get(path, def, DataFunction.intValue()); return get(path, def, DataFunction.intValue());
} }
/**
* Predicate the value of given path is a {@link Long}.F
*
* @param path The path to check.
* @return True if the value is present and is a long, false otherwise.
*/
default boolean isLong(@NotNull String path) { default boolean isLong(@NotNull String path) {
return isType(path, Long.class); return isType(path, Long.class);
} }
/**
* Get the value as a {@link Long} from the specified path.
*
* @param path The path to get the long.
* @return The long if the path exists and is a long, otherwise 0.
*/
default @Nullable Long getLong(@NotNull String path) { default @Nullable Long getLong(@NotNull String path) {
return getLong(path, 0L); return getLong(path, 0L);
} }
/**
* Get the value as a {@link Long} from the specified path.
*
* @param path The path to get the long.
* @param def The default value to return if the path does not exist.
* @return The long if the path exists and is a long, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Long getLong(@NotNull String path, @Nullable Long def) { default @Nullable Long getLong(@NotNull String path, @Nullable Long def) {
return get(path, def, DataFunction.longValue()); return get(path, def, DataFunction.longValue());
} }
/**
* Predicate the value of given path is a {@link Float}.
*
* @param path The path to check.
* @return True if the value is present and is a float, false otherwise.
*/
default boolean isFloat(@NotNull String path) { default boolean isFloat(@NotNull String path) {
return isType(path, Float.class); return isType(path, Float.class);
} }
/**
* Get the value as a {@link Float} from the specified path.
*
* @param path The path to get the float.
* @return The float if the path exists and is a float, otherwise 0.
*/
default @Nullable Float getFloat(@NotNull String path) { default @Nullable Float getFloat(@NotNull String path) {
return getFloat(path, 0.0F); return getFloat(path, 0.0F);
} }
/**
* Get the value as a {@link Float} from the specified path.
*
* @param path The path to get the float.
* @param def The default value to return if the path does not exist.
* @return The float if the path exists and is a float, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) { default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) {
return get(path, def, DataFunction.floatValue()); return get(path, def, DataFunction.floatValue());
} }
/**
* Predicate the value of given path is a {@link Double}.
*
* @param path The path to check.
* @return True if the value is present and is a double, false otherwise.
*/
default boolean isDouble(@NotNull String path) { default boolean isDouble(@NotNull String path) {
return isType(path, Double.class); return isType(path, Double.class);
} }
/**
* Get the value as a {@link Double} from the specified path.
*
* @param path The path to get the double.
* @return The double if the path exists and is a double, otherwise 0.
*/
default @Nullable Double getDouble(@NotNull String path) { default @Nullable Double getDouble(@NotNull String path) {
return getDouble(path, 0.0D); return getDouble(path, 0.0D);
} }
/**
* Get the value as a {@link Double} from the specified path.
*
* @param path The path to get the double.
* @param def The default value to return if the path does not exist.
* @return The double if the path exists and is a double, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) { default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) {
return get(path, def, DataFunction.doubleValue()); return get(path, def, DataFunction.doubleValue());
} }
/**
* Predicate the value of given path is a {@link Character}.
*
* @param path The path to check.
* @return True if the value is present and is a char, false otherwise.
*/
default boolean isChar(@NotNull String path) { default boolean isChar(@NotNull String path) {
return isType(path, Boolean.class); return isType(path, Boolean.class);
} }
/**
* Get the value as a {@link Character} from the specified path.
*
* @param path The path to get the char.
* @return The char if the path exists and is a char, otherwise null.
*/
default @Nullable Character getChar(@NotNull String path) { default @Nullable Character getChar(@NotNull String path) {
return getChar(path, null); return getChar(path, null);
} }
/**
* Get the value as a {@link Character} from the specified path.
*
* @param path The path to get the char.
* @param def The default value to return if the path does not exist.
* @return The char if the path exists and is a char, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Character getChar(@NotNull String path, @Nullable Character def) { default @Nullable Character getChar(@NotNull String path, @Nullable Character def) {
return get(path, def, Character.class); return get(path, def, Character.class);
} }
/**
* Predicate the value of given path is a {@link String}.
*
* @param path The path to check.
* @return True if the value is present and is a string, false otherwise.
*/
default boolean isString(@NotNull String path) { default boolean isString(@NotNull String path) {
return isType(path, String.class); return isType(path, String.class);
} }
/**
* Get the value as a {@link String} from the specified path.
*
* @param path The path to get the string.
* @return The string if the path exists and is a string, otherwise null.
*/
default @Nullable String getString(@NotNull String path) { default @Nullable String getString(@NotNull String path) {
return getString(path, null); return getString(path, null);
} }
/**
* Get the value as a {@link String} from the specified path.
*
* @param path The path to get the string.
* @param def The default value to return if the path does not exist.
* @return The string if the path exists and is a string, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable String getString(@NotNull String path, @Nullable String def) { default @Nullable String getString(@NotNull String path, @Nullable String def) {
return get(path, def, String.class); return get(path, def, String.class);
@@ -298,23 +831,53 @@ public interface ConfigureSection {
return getList(path, DataFunction.castObject(Character.class)); return getList(path, DataFunction.castObject(Character.class));
} }
/**
* Get the specific type of collection from the section.
*
* @param path The path to get the collection from
* @param constructor The constructor of the collection
* @param parser The function to parse the values
* @param <T> The type of the values
* @param <C> The type of the collection
* @return The collection of values
*/
default <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path, default <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path,
@NotNull Supplier<C> constructor, @NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> parser) { @NotNull DataFunction<Object, T> parser) {
return parseCollection(getList(path), constructor, parser); return parseCollection(getList(path), constructor, parser);
} }
/**
* Get the specific type of steam from the section.
*
* @param path The path to get the stream from
* @return The stream of values
*/
default @NotNull Stream<?> stream(@NotNull String path) { default @NotNull Stream<?> stream(@NotNull String path) {
List<?> values = getList(path); List<?> values = getList(path);
return values == null ? Stream.empty() : values.stream(); return values == null ? Stream.empty() : values.stream();
} }
/**
* Get the specific type of steam from the section.
*
* @param path The path to get the stream from
* @param parser The function to parse the values
* @param <T> The type of the values
* @return The stream of values
*/
default <T> @NotNull Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) { default <T> @NotNull Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) {
return stream(path).map(parser); return stream(path).map(parser);
} }
default String childPath(String path) {
int index = path.indexOf(pathSeparator());
return (index == -1) ? path : path.substring(index + 1);
}
static <T, C extends Collection<T>> @NotNull C parseCollection( static <T, C extends Collection<T>> @NotNull C parseCollection(
@Nullable List<?> data, @NotNull Supplier<C> constructor, @Nullable List<?> data,
@NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> parser @NotNull DataFunction<Object, T> parser
) { ) {
C values = constructor.get(); C values = constructor.get();
@@ -1,12 +1,21 @@
package cc.carm.lib.configuration.source.section; package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull; import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.*;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/**
* ConfigureSource represents the source of configuration,
* which can be a file, a database, or any other source.
*
* @param <SECTION> The type of the root section.
* @param <ORIGINAL> The original configuration object.
* @param <SELF> The type of the source itself, for further implement support.
* @see ConfigureSection
*/
public abstract class ConfigureSource< public abstract class ConfigureSource<
SECTION extends ConfigureSection, ORIGINAL, SECTION extends ConfigureSection, ORIGINAL,
SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>> SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>>
@@ -24,28 +33,46 @@ public abstract class ConfigureSource<
return holder; return holder;
} }
public void reload() throws Exception { public void reload() throws Exception {
onReload(); // 调用重写的Reload方法 onReload(); // 调用重写的Reload方法
this.lastUpdateMillis = System.currentTimeMillis(); this.lastUpdateMillis = System.currentTimeMillis();
} }
protected abstract SELF self(); @Contract(pure = true)
@ApiStatus.Internal
protected abstract @NotNull SELF self();
/** /**
* @return Original configuration object * @return The original configuration object.
*/ */
@Contract(pure = true)
public abstract @NotNull ORIGINAL original(); public abstract @NotNull ORIGINAL original();
/** /**
* @return The root {@link ConfigureSection} * @return The root {@link ConfigureSection}, which represents the entire configuration.
*/ */
public abstract @NotNull SECTION section(); public abstract @NotNull SECTION section();
/**
* Save the whole configuration.
*
* @throws Exception If any error occurs while saving.
*/
public abstract void save() throws Exception; public abstract void save() throws Exception;
/**
* Reload the configuration.
* <br>This used for implementation, for external usage, use {@link #reload()}
*
* @throws Exception If any error occurs while reloading.
*/
@ApiStatus.OverrideOnly
protected abstract void onReload() throws Exception; protected abstract void onReload() throws Exception;
public char pathSeparator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public long getLastUpdateMillis() { public long getLastUpdateMillis() {
return this.lastUpdateMillis; return this.lastUpdateMillis;
} }
@@ -54,14 +81,20 @@ public abstract class ConfigureSource<
return getLastUpdateMillis() > parsedTime; return getLastUpdateMillis() > parsedTime;
} }
/**
* Source also represents the root section, so it has no parent
*
* @return null
*/
@Override @Override
@Contract(pure = true, value = "->null")
public @Nullable ConfigureSection parent() { public @Nullable ConfigureSection parent() {
return null; // Source also represents the root section, so it has no parent return null;
} }
@Override @Override
public @NotNull ConfigureSource<?, ?, ?> source() { public @NotNull String path() {
return self(); return "";
} }
@Override @Override
@@ -69,34 +102,24 @@ public abstract class ConfigureSource<
return section().getValues(deep); return section().getValues(deep);
} }
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return section().getKeys(deep);
}
@Override
public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return section().createSection(path, data);
}
@Override @Override
public void set(@NotNull String path, @Nullable Object value) { public void set(@NotNull String path, @Nullable Object value) {
section().set(path, value); section().set(path, value);
} }
@Override @Override
public boolean contains(@NotNull String path) { public void remove(@NotNull String path) {
return section().contains(path); section().remove(path);
}
@Override
public boolean isList(@NotNull String path) {
return section().isList(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return section().getList(path);
}
@Override
public boolean isSection(@NotNull String path) {
return section().isSection(path);
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
return section().getSection(path);
} }
@Override @Override
@@ -113,7 +113,7 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
* @param override Whether to overwrite existing configured value * @param override Whether to overwrite existing configured value
*/ */
public void setDefault(boolean override) { public void setDefault(boolean override) {
if (!override && config().contains(path())) return; if (config().contains(path()) && !override) return; // Skip if the value is already set
set(defaults()); // Set the default value set(defaults()); // Set the default value
} }
@@ -4,6 +4,7 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -20,7 +21,6 @@ public class ValueManifest<T> {
protected @NotNull Supplier<@Nullable T> defaultSupplier; protected @NotNull Supplier<@Nullable T> defaultSupplier;
public ValueManifest(@NotNull ValueType<T> type) { public ValueManifest(@NotNull ValueType<T> type) {
this(type, () -> null, EMPTY_INITIALIZER, null, null); this(type, () -> null, EMPTY_INITIALIZER, null, null);
} }
@@ -93,12 +93,12 @@ public class ValueManifest<T> {
public @NotNull String path() { public @NotNull String path() {
if (path != null) return path; if (path != null) return path;
else throw new IllegalStateException("No section path provided."); else throw new IllegalStateException("No section path provided for Value(" + type() + ").");
} }
public @NotNull ConfigurationHolder<?> holder() { public @NotNull ConfigurationHolder<?> holder() {
if (this.holder != null) return this.holder; if (this.holder != null) return this.holder;
throw new IllegalStateException("Value does not have a provider."); throw new IllegalStateException("Value(" + type() + ") does not have a provider.");
} }
public @NotNull ConfigureSource<?, ?, ?> config() { public @NotNull ConfigureSource<?, ?, ?> config() {
@@ -109,16 +109,17 @@ public class ValueManifest<T> {
return holder().metadata(path()); return holder().metadata(path());
} }
@ApiStatus.Internal
protected Object getData() { protected Object getData() {
return config().get(path()); return config().get(path());
} }
@ApiStatus.Internal
protected void setData(@Nullable Object value) { protected void setData(@Nullable Object value) {
config().set(path(), value); config().set(path(), value);
} }
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, path) -> {
}; };
} }
+14 -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.1</version> <version>4.0.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -33,6 +33,19 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>4.0.8</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -7,7 +7,7 @@ import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue; import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"}) @HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"})
public class DatabaseConfiguration implements Configuration { public interface DatabaseConfiguration extends Configuration {
@ConfigPath("driver") @ConfigPath("driver")
@HeaderComments({ @HeaderComments({
@@ -16,18 +16,19 @@ public class DatabaseConfiguration implements Configuration {
"- MySQL(新): com.mysql.cj.jdbc.Driver", "- MySQL(新): com.mysql.cj.jdbc.Driver",
"- MariaDB(推荐): org.mariadb.jdbc.Driver", "- MariaDB(推荐): org.mariadb.jdbc.Driver",
}) })
protected static final ConfigValue<String> DRIVER_NAME = ConfiguredValue.of( ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
String.class, "com.mysql.cj.jdbc.Driver" String.class, "com.mysql.cj.jdbc.Driver"
); );
protected static final ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1"); ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
protected static final ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306); ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
protected static final ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft"); ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
protected static final ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root"); ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
protected static final ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password"); ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static final ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
protected static String buildJDBC() { ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
static String buildJDBC() {
return String.format("jdbc:mysql://%s:%s/%s%s", HOST.get(), PORT.get(), DATABASE.get(), EXTRA.get()); return String.format("jdbc:mysql://%s:%s/%s%s", HOST.get(), PORT.get(), DATABASE.get(), EXTRA.get());
} }
@@ -69,14 +69,14 @@ public class ConfigurationTest {
provider.initialize(TEST); provider.initialize(TEST);
System.out.println("> Test Inner value before:"); System.out.println("> Test Inner value before:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve()); System.out.println(TEST.INSTANCE.STATUS.resolve());
double after = Math.random() * 200D; double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after); System.out.println("> Test Inner value -> " + after);
TEST.INSTANCE.INNER_VALUE.set(after); TEST.INSTANCE.STATUS.set(after);
System.out.println("> Test Inner value after:"); System.out.println("> Test Inner value after:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve()); System.out.println(TEST.INSTANCE.STATUS.resolve());
} }
@@ -1,11 +1,7 @@
package cc.carm.lib.configuration.demo.tests.conf; package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath; import cc.carm.lib.configuration.annotation.*;
import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.demo.tests.model.ItemStack; import cc.carm.lib.configuration.demo.tests.model.ItemStack;
import cc.carm.lib.configuration.demo.tests.model.UserRecord; import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
@@ -29,7 +25,8 @@ import java.util.UUID;
public interface DemoConfiguration extends Configuration { public interface DemoConfiguration extends Configuration {
@ConfigPath(root = true) @ConfigPath(root = true)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 1.0D); @ConfigVersion(2)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 2.0D);
@ConfigPath(root = true) @ConfigPath(root = true)
@FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"}) @FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"})
@@ -39,10 +36,6 @@ public interface DemoConfiguration extends Configuration {
@FooterComments({"上述的枚举内容本质上是通过STRING解析的"}) @FooterComments({"上述的枚举内容本质上是通过STRING解析的"})
ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS); ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
// 支持通过 Class<?> 变量标注子配置,一并注册。
// 注意: 若对应类也有注解,则优先使用类的注解。
Class<?> DATABASE = DatabaseConfiguration.class;
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。 @ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。 @HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。 @InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。
@@ -8,6 +8,6 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments("Inner Test") @HeaderComments("Inner Test")
public class InstanceConfig implements Configuration { public class InstanceConfig implements Configuration {
public final ConfigValue<Double> INNER_VALUE = ConfiguredValue.of(1.0D); public final ConfigValue<Double> STATUS = ConfiguredValue.of(1.0D);
} }
@@ -20,7 +20,7 @@ public class RegistryConfig implements Configuration {
@FooterComments({"12313213212"}) @FooterComments({"12313213212"})
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。 @InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。 @InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。
public final ConfigValue<UserRecord> TEST_MODEL = ConfiguredValue.builderOf(UserRecord.class).fromSection() public final ConfigValue<UserRecord> OWNER = ConfiguredValue.builderOf(UserRecord.class).fromSection()
.defaults(new UserRecord("Carm", UUID.randomUUID())) .defaults(new UserRecord("Carm", UUID.randomUUID()))
.parse((holder, section) -> UserRecord.deserialize(section)) .parse((holder, section) -> UserRecord.deserialize(section))
.serialize((holder, data) -> data.serialize()).build(); .serialize((holder, data) -> data.serialize()).build();
@@ -36,6 +36,7 @@ public class UserRecord extends AbstractRecord {
} }
public static UserRecord deserialize(ConfigureSection section) { public static UserRecord deserialize(ConfigureSection section) {
System.out.println("> Deserializing -> " + section.fullPath());
String name = section.getString("name"); String name = section.getString("name");
if (name == null) throw new NullPointerException("name is null"); if (name == null) throw new NullPointerException("name is null");
String uuidString = section.getString("info.uuid"); String uuidString = section.getString("info.uuid");
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.1</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -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();
} }
@@ -26,7 +26,7 @@ public class Commentable {
String comment = getInlineComment(holder, path, null); String comment = getInlineComment(holder, path, null);
if (comment != null) return comment; if (comment != null) return comment;
String sep = String.valueOf(holder.options().get(StandardOptions.PATH_SEPARATOR)); String sep = String.valueOf(holder.option(StandardOptions.PATH_SEPARATOR));
// If the comment is not found, try to get the comment from the parent section // If the comment is not found, try to get the comment from the parent section
String[] keys = path.split(Pattern.quote(sep)); String[] keys = path.split(Pattern.quote(sep));
+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.1</version> <version>4.0.8</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.1</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -0,0 +1,188 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implements ConfigureSection {
protected final @NotNull Map<String, Object> data;
protected final @Nullable R parent;
protected final @NotNull String path;
protected AbstractMapSection(@Nullable R parent, @NotNull String path) {
this.parent = parent;
this.path = path;
this.data = new LinkedHashMap<>();
}
public void migrate(Map<?, ?> data) {
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, createSection(key, (Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>();
int index = 0;
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) {
list.add(createSection(key + "[" + index + "]", (Map<?, ?>) obj));
} else {
list.add(obj);
}
index++;
}
this.data.put(key, list);
} else {
this.data.put(key, entry.getValue());
}
}
}
public abstract @NotNull R self();
@Override
public abstract @NotNull R createSection(@NotNull String path, @NotNull Map<?, ?> data);
@Override
public @NotNull String path() {
return this.path;
}
@Override
public boolean contains(@NotNull String path) {
R section = getSectionFor(path);
if (section == this) {
return this.data().containsKey(path);
} else {
return section.contains(childPath(path));
}
}
public @NotNull Map<String, Object> data() {
return this.data;
}
@Override
public boolean isEmpty() {
return this.data.isEmpty();
}
@Override
public int size(boolean deep) {
return deep ? getKeys(true).size() : this.data.size();
}
@UnmodifiableView
public @NotNull Map<String, Object> rawMap() {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : this.data.entrySet()) {
if (entry.getValue() instanceof AbstractMapSection<?>) {
output.put(entry.getKey(), ((AbstractMapSection<?>) entry.getValue()).rawMap());
} else if (entry.getValue() instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof AbstractMapSection<?>) {
list.add(((AbstractMapSection<?>) obj).rawMap());
} else {
list.add(obj);
}
}
output.put(entry.getKey(), list);
} else {
output.put(entry.getKey(), entry.getValue());
}
}
return output;
}
public @Nullable R parent() {
return this.parent;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mappingValues(this, null, true, String.valueOf(pathSeparator())) : data());
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return Collections.unmodifiableSet(deep ? mappingKeys(this, null, true, String.valueOf(pathSeparator())) : data().keySet());
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) value = createSection(path, (Map<?, ?>) value);
R section = getSectionFor(path);
if (section == this) {
// Even this value is null, we still need to put it in the map
// to ensure that the path is marked as existing.
this.data.put(path, value);
} else {
section.set(childPath(path), value);
}
}
@Override
public void remove(@NotNull String path) {
R section = getSectionFor(path);
if (section != this) {
section.remove(childPath(path));
} else {
this.data.remove(path);
}
}
@Override
public @Nullable Object get(@NotNull String path) {
R section = getSectionFor(path);
return section == this ? data.get(path) : section.get(childPath(path));
}
@SuppressWarnings("unchecked")
private R getSectionFor(String path) {
int index = path.indexOf(pathSeparator());
if (index == -1) return self();
String root = path.substring(0, index);
return (R) computeSection(root);
}
/**
* Map the values of the children of the section to the output map.
*
* @param section The section to map the values from
* @param parent The parent path
* @param deep If the mapping should be deep
*/
protected static Map<String, Object> mappingValues(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep, String pathSeparator) {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof AbstractMapSection<?>) {
output.putAll(mappingValues((AbstractMapSection<?>) entry.getValue(), path, true, pathSeparator));
}
}
return output;
}
protected static Set<String> mappingKeys(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep, String pathSeparator) {
Set<String> keys = new LinkedHashSet<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator) + entry.getKey();
keys.add(path);
if (deep && entry.getValue() instanceof AbstractMapSection<?>) {
keys.addAll(mappingKeys((AbstractMapSection<?>) entry.getValue(), path, true, pathSeparator));
}
}
return keys;
}
}
@@ -0,0 +1,377 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class ImmutableSection implements ConfigureSection {
public static ImmutableSection of(@NotNull ConfigureSection section) {
if (section instanceof ImmutableSection) {
return (ImmutableSection) section;
} else return new ImmutableSection(null, section);
}
protected final @Nullable ImmutableSection parent;
protected final @NotNull ConfigureSection section;
private ImmutableSection(@Nullable ImmutableSection parent, @NotNull ConfigureSection section) {
this.parent = parent;
this.section = section;
}
@Override
public @Nullable ImmutableSection parent() {
return this.parent;
}
@Override
public @NotNull String path() {
return section().path();
}
private @NotNull ConfigureSection section() {
return section;
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) {
return section().getValues(deep);
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
throw new IllegalStateException("This section is not modifiable!");
}
@Override
public void remove(@NotNull String path) {
throw new IllegalStateException("This section is not modifiable!");
}
@Override
public @NotNull ImmutableSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new ImmutableSection(this, section().createSection(path, data));
}
@Override
public @Nullable Object get(@NotNull String path) {
Object value = section().get(path);
if (value instanceof ConfigureSection && !(value instanceof ImmutableSection)) {
return new ImmutableSection(this, (ConfigureSection) value);
}
return value;
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
ConfigureSection get = section().getSection(path);
if (get != null && !(get instanceof ImmutableSection)) {
return new ImmutableSection(this, get);
}
return get;
}
@Override
public char pathSeparator() {
return section().pathSeparator();
}
@Override
public boolean isRoot() {
return section().isRoot();
}
@Override
public boolean isEmpty() {
return section().isEmpty();
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return section().getKeys(deep);
}
@Override
public @NotNull @UnmodifiableView Set<String> keys() {
return section().keys();
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> values() {
return section().values();
}
@Override
public Stream<Map.Entry<String, Object>> stream() {
return section().stream();
}
@Override
public void forEach(@NotNull BiConsumer<String, Object> action) {
section().forEach(action);
}
@Override
public boolean contains(@NotNull String path) {
return section().contains(path);
}
@Override
public boolean containsValue(@NotNull String path) {
return section().containsValue(path);
}
@Override
public <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) {
return section().isType(path, typeClass);
}
@Override
public boolean isList(@NotNull String path) {
return section().isList(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return section().getList(path);
}
@Override
public boolean isSection(@NotNull String path) {
return section().isSection(path);
}
@Override
public <T> @Nullable T get(@NotNull String path, @NotNull Class<T> type) {
return section().get(path, type);
}
@Override
public <T> @Nullable T get(@NotNull String path, @NotNull DataFunction<@Nullable Object, T> parser) {
return section().get(path, parser);
}
@Override
public <T> @Nullable T get(@NotNull String path, @Nullable T defaults, @NotNull Class<T> clazz) {
return section().get(path, defaults, clazz);
}
@Override
public <T> @Nullable T get(@NotNull String path, @Nullable T defaultValue, @NotNull DataFunction<Object, T> parser) {
return section().get(path, defaultValue, parser);
}
@Override
public boolean isBoolean(@NotNull String path) {
return section().isBoolean(path);
}
@Override
public boolean getBoolean(@NotNull String path) {
return section().getBoolean(path);
}
@Override
public @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) {
return section().getBoolean(path, def);
}
@Override
public @Nullable Boolean isByte(@NotNull String path) {
return section().isByte(path);
}
@Override
public @Nullable Byte getByte(@NotNull String path) {
return section().getByte(path);
}
@Override
public @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) {
return section().getByte(path, def);
}
@Override
public boolean isShort(@NotNull String path) {
return section().isShort(path);
}
@Override
public @Nullable Short getShort(@NotNull String path) {
return section().getShort(path);
}
@Override
public @Nullable Short getShort(@NotNull String path, @Nullable Short def) {
return section().getShort(path, def);
}
@Override
public boolean isInt(@NotNull String path) {
return section().isInt(path);
}
@Override
public @Nullable Integer getInt(@NotNull String path) {
return section().getInt(path);
}
@Override
public @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
return section().getInt(path, def);
}
@Override
public boolean isLong(@NotNull String path) {
return section().isLong(path);
}
@Override
public @Nullable Long getLong(@NotNull String path) {
return section().getLong(path);
}
@Override
public @Nullable Long getLong(@NotNull String path, @Nullable Long def) {
return section().getLong(path, def);
}
@Override
public boolean isFloat(@NotNull String path) {
return section().isFloat(path);
}
@Override
public @Nullable Float getFloat(@NotNull String path) {
return section().getFloat(path);
}
@Override
public @Nullable Float getFloat(@NotNull String path, @Nullable Float def) {
return section().getFloat(path, def);
}
@Override
public boolean isDouble(@NotNull String path) {
return section().isDouble(path);
}
@Override
public @Nullable Double getDouble(@NotNull String path) {
return section().getDouble(path);
}
@Override
public @Nullable Double getDouble(@NotNull String path, @Nullable Double def) {
return section().getDouble(path, def);
}
@Override
public boolean isChar(@NotNull String path) {
return section().isChar(path);
}
@Override
public @Nullable Character getChar(@NotNull String path) {
return section().getChar(path);
}
@Override
public @Nullable Character getChar(@NotNull String path, @Nullable Character def) {
return section().getChar(path, def);
}
@Override
public boolean isString(@NotNull String path) {
return section().isString(path);
}
@Override
public @Nullable String getString(@NotNull String path) {
return section().getString(path);
}
@Override
public @Nullable String getString(@NotNull String path, @Nullable String def) {
return section().getString(path, def);
}
@Override
public @NotNull <V> List<V> getList(@NotNull String path, @NotNull DataFunction<Object, V> parser) {
return section().getList(path, parser);
}
@Override
public @NotNull List<String> getStringList(@NotNull String path) {
return section().getStringList(path);
}
@Override
public @NotNull List<Integer> getIntegerList(@NotNull String path) {
return section().getIntegerList(path);
}
@Override
public @NotNull List<Long> getLongList(@NotNull String path) {
return section().getLongList(path);
}
@Override
public @NotNull List<Double> getDoubleList(@NotNull String path) {
return section().getDoubleList(path);
}
@Override
public @NotNull List<Float> getFloatList(@NotNull String path) {
return section().getFloatList(path);
}
@Override
public @NotNull List<Byte> getByteList(@NotNull String path) {
return section().getByteList(path);
}
@Override
public @NotNull List<Character> getCharList(@NotNull String path) {
return section().getCharList(path);
}
@Override
public <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path, @NotNull Supplier<C> constructor, @NotNull DataFunction<Object, T> parser) {
return section().getCollection(path, constructor, parser);
}
@Override
public @NotNull Stream<?> stream(@NotNull String path) {
return section().stream(path);
}
@Override
public @NotNull <T> Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) {
return section().stream(path, parser);
}
@Override
public String childPath(String path) {
return section().childPath(path);
}
@Override
public int hashCode() {
return section.hashCode();
}
@Override
public boolean equals(Object obj) {
return Objects.equals(section, obj);
}
}
@@ -3,148 +3,54 @@ package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class MemorySection implements ConfigureSection { public class MemorySection extends AbstractMapSection<MemorySection> {
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source) { public static MemorySection of() {
return new MemorySection(source, new LinkedHashMap<>(), null); return of(new LinkedHashMap<>());
} }
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source, public static MemorySection of(@NotNull Consumer<Map<String, Object>> data) {
@Nullable Map<?, ?> data) { return of(() -> {
return new MemorySection(source, data == null ? new LinkedHashMap<>() : data, null); Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
} }
protected final @NotNull ConfigureSource<? extends MemorySection, ?, ?> source; public static MemorySection of(@NotNull Supplier<Map<?, ?>> data) {
protected final @NotNull Map<String, Object> data; return of(data.get());
protected final @Nullable MemorySection parent;
public MemorySection(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@NotNull Map<?, ?> data, @Nullable MemorySection parent) {
this.source = source;
this.parent = parent;
this.data = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, createChild((Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) {
list.add(createChild((Map<?, ?>) obj));
} else {
list.add(obj);
}
}
this.data.put(key, list);
} else {
this.data.put(key, entry.getValue());
}
}
} }
protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) { public static MemorySection of(@NotNull Map<?, ?> data) {
return new MemorySection(source(), data, this); return of(data, null, "");
} }
protected @NotNull MemorySection createChild() { public static MemorySection of(@Nullable MemorySection parent, @NotNull String path) {
return createChild(new LinkedHashMap<>()); return of(new LinkedHashMap<>(), parent, path);
}
public static MemorySection of(@NotNull Map<?, ?> data, @Nullable MemorySection parent, @NotNull String path) {
return new MemorySection(data, parent, path);
}
public MemorySection(@NotNull Map<?, ?> raw, @Nullable MemorySection parent, @NotNull String path) {
super(parent, path);
migrate(raw);
} }
@Override @Override
public @NotNull ConfigureSource<? extends MemorySection, ?, ?> source() { public @NotNull MemorySection self() {
return this.source; return this;
}
public @NotNull Map<String, Object> data() {
return this.data;
}
public @Nullable MemorySection parent() {
return this.parent;
} }
@Override @Override
public @NotNull Map<String, Object> getValues(boolean deep) { public @NotNull MemorySection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return Collections.unmodifiableMap(deep ? mapChildrenValues(this, null, true) : data()); return new MemorySection(data, this, path);
} }
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) value = createChild((Map<?, ?>) value);
MemorySection section = getSectionFor(path);
if (section == this) {
// Even this value is null, we still need to put it in the map
// to ensure that the path is marked as existing.
this.data.put(path, value);
} else {
section.set(childPath(path), value);
}
}
@Override
public boolean contains(@NotNull String path) {
return get(path) != null;
}
@Override
public @Nullable Object get(@NotNull String path) {
MemorySection section = getSectionFor(path);
return section == this ? data.get(path) : section.get(childPath(path));
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
Object val = get(path);
return (val instanceof List<?>) ? (List<?>) val : null;
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
}
private MemorySection getSectionFor(String path) {
int index = path.indexOf(separator());
if (index == -1) return this;
String root = path.substring(0, index);
Object section = this.data.get(root);
if (section == null) {
section = createChild();
this.data.put(root, section);
}
return (MemorySection) section;
}
private String childPath(String path) {
int index = path.indexOf(separator());
return (index == -1) ? path : path.substring(index + 1);
}
/**
* Map the values of the children of the section to the output map.
*
* @param section The section to map the values from
* @param parent The parent path
* @param deep If the mapping should be deep
*/
protected Map<String, Object> mapChildrenValues(@NotNull MemorySection section,
@Nullable String parent, boolean deep) {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + separator()) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof MemorySection) {
output.putAll(mapChildrenValues((MemorySection) entry.getValue(), path, true));
}
}
return output;
}
} }
@@ -0,0 +1,145 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
public class ShadedSection implements ConfigureSection {
public static ShadedSection create(@NotNull ConfigureSection template, @Nullable ConfigureSection source) {
return new ShadedSection(null, template, source);
}
protected final @Nullable ShadedSection parent;
protected @NotNull ConfigureSection template;
protected @Nullable ConfigureSection source;
public ShadedSection(@Nullable ShadedSection parent,
@NotNull ConfigureSection template, @Nullable ConfigureSection source) {
this.parent = parent;
this.template = template;
this.source = source;
}
@Override
public @Nullable ConfigureSection parent() {
return this.parent;
}
@Override
public @NotNull String path() {
return this.template.path();
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) {
if (source == null) return template.getValues(deep);
// 本函数为,当 getValues 时,递归合并 source 和 template
return merge(template, source).getValues(deep);
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
Set<String> keys = new HashSet<>(template.getKeys(deep));
if (source != null) {
keys.addAll(source.getKeys(deep));
}
return Collections.unmodifiableSet(keys);
}
private ConfigureSection merge(ConfigureSection templateSection, ConfigureSection valueSection) {
MemorySection merged = MemorySection.of();
Set<String> existingKey = new HashSet<>();
for (Map.Entry<String, Object> entry : valueSection.getValues(false).entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof ConfigureSection) {
ConfigureSection subSectionFromValue = (ConfigureSection) value;
ConfigureSection subSectionFromTemplate = (ConfigureSection) templateSection.get(key);
if (subSectionFromTemplate == null) {
merged.set(key, value);
} else {
merged.set(key, merge(subSectionFromTemplate, subSectionFromValue));
}
} else {
merged.set(key, value);
}
existingKey.add(key);
}
for (Map.Entry<String, Object> entry : templateSection.getValues(false).entrySet()) {
if (existingKey.contains(entry.getKey())) continue;
merged.set(entry.getKey(), entry.getValue());
}
return merged;
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof ConfigureSection) {
ConfigureSection targetSection = (ConfigureSection) value;
for (Map.Entry<String, Object> entry : targetSection.getValues(true).entrySet()) {
set(path + pathSeparator() + entry.getKey(), entry.getValue());
}
return;
} else if (Objects.equals(get(path), value)) {
remove(path);
return;
}
Optional.ofNullable(source).ifPresent(s -> s.set(path, value));
}
@Override
public void remove(@NotNull String path) {
Optional.ofNullable(source).ifPresent(s -> s.remove(path));
}
@Override
public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
if (source == null) {
return new ShadedSection(this, template, MemorySection.of(data));
} else {
ConfigureSection section = source.computeSection(path, data);
return new ShadedSection(this, template, section);
}
}
@Override
public @Nullable Object get(@NotNull String path) {
if (source == null) {
return getFromTemplate(path);
}
Object value = source.get(path);
if (value == null) {
return getFromTemplate(path);
}
if (value instanceof ConfigureSection) {
ConfigureSection templateSection = (ConfigureSection) template.get(path);
if (templateSection == null) {
return value;
} else {
return new ShadedSection(this, templateSection, (ConfigureSection) value);
}
}
return value;
}
public @Nullable Object getFromTemplate(@NotNull String path) {
Object value = template.get(path);
if (value instanceof ConfigureSection) {
return new ShadedSection(this, (ConfigureSection) value, null);
} else {
return value;
}
}
public @Nullable ConfigureSection getSource() {
return source;
}
}
@@ -0,0 +1,48 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
public class SourcedSection extends AbstractMapSection<SourcedSection> {
public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source) {
return new SourcedSection(source, new LinkedHashMap<>(), null, "");
}
public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source,
@Nullable Map<?, ?> raw) {
return new SourcedSection(source, raw == null ? new LinkedHashMap<>() : raw, null, "");
}
protected final @NotNull ConfigureSource<? extends SourcedSection, ?, ?> source;
public SourcedSection(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source,
@NotNull Map<?, ?> raw, @Nullable SourcedSection parent, @NotNull String path) {
super(parent, path);
this.source = source;
migrate(raw);
}
public @NotNull ConfigureSource<? extends SourcedSection, ?, ?> source() {
return source;
}
@Override
public char pathSeparator() {
return source().pathSeparator();
}
@Override
public @NotNull SourcedSection self() {
return this;
}
@Override
public @NotNull SourcedSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new SourcedSection(source(), data, this, path);
}
}
@@ -0,0 +1,77 @@
package test.section;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.MemorySection;
import cc.carm.lib.configuration.source.section.ShadedSection;
import org.junit.Test;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Lyzen
* @date 21/2/2025 下午2:18
*/
public class ShadeTest {
@Test
public void test() {
ConfigureSection template = MemorySection.of(data -> {
data.put("name", "GentleMan");
data.put("age", 12);
data.put("gender", "male");
Map<String, Object> address = new LinkedHashMap<>();
address.put("Hotel", "Nanjing Road 101");
address.put("Store", "Beijing Road 404");
Map<String, Object> mapInside = new LinkedHashMap<>();
mapInside.put("InsideKeyExample", "InsideValueExample");
address.put("Inside", mapInside);
data.put("addresses", address);
data.put("cards", Arrays.asList("00000", "11111", "22222"));
});
ConfigureSection source = MemorySection.of(data -> {
data.put("age", 25);
Map<String, Object> address = new LinkedHashMap<>();
address.put("NewOne", "Guangdong Road 505");
data.put("addresses", address);
Map<String, Object> mapInside = new LinkedHashMap<>();
mapInside.put("AnotherInsideKey", "AnotherInsideValue");
address.put("Inside", mapInside);
data.put("cards", Arrays.asList("33333", "55555")); // 应当直接覆盖原先的List
});
ShadedSection root = new ShadedSection(null, template, source);
System.out.println("age: " + root.get("age"));
System.out.println("addresses: ");
for (Map.Entry<String, Object> entry : root.getSection("addresses").getValues(false).entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
if (entry.getValue() instanceof ConfigureSection) {
for (Map.Entry<String, Object> inner : ((ConfigureSection) entry.getValue()).getValues(false).entrySet()) {
System.out.println(" " + inner.getKey() + ": " + inner.getValue());
}
}
}
System.out.println("cards: " + root.getList("cards"));
System.out.println("\n----------------------\n");
System.out.println("Deep Search Test");
System.out.println("addresses: ");
for (Map.Entry<String, Object> entry : root.getSection("addresses").getValues(true).entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
System.out.println("\n----------------------\n");
root.set("addresses", MemorySection.of(map -> {
map.put("Hotel", "Nanjing Road 101");
map.put("Store", "Beijing Road banned");
}));
for (Map.Entry<String, Object> entry : source.getValues(true).entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
}
+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.1</version> <version>4.0.8</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.1</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.annotation; package cc.carm.lib.configuration.annotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Range;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@@ -22,7 +21,7 @@ public @interface ConfigVersion {
* *
* @return the version of the configuration field * @return the version of the configuration field
*/ */
@Range(from = 0, to = Integer.MAX_VALUE) @Range(from = 0, to = 65535)
int value() default 0; int value() default 0;
} }
@@ -1,13 +0,0 @@
package cc.carm.lib.configuration.option;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface VersionedOptions {
/**
* Whether to set newer defaults when a {@link cc.carm.lib.configuration.value.ConfigValue}'s marked version
* is newer than the current in storage.
*/
ConfigurationOption<Boolean> RESET_NEWER_DEFAULTS = ConfigurationOption.of(true);
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.commentable; package cc.carm.lib.configuration.versioned;
import cc.carm.lib.configuration.annotation.ConfigVersion; import cc.carm.lib.configuration.annotation.ConfigVersion;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
@@ -19,7 +19,18 @@ public interface VersionedMetaTypes {
} }
static void register(@NotNull ConfigurationInitializer initializer) { static void register(@NotNull ConfigurationInitializer initializer) {
initializer.registerFieldAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value); initializer.appendFieldInitializer((holder, path, field, value) -> {
ConfigVersion annotation = field.getAnnotation(ConfigVersion.class);
if (annotation == null || value == null) return;
int currentVersion = annotation.value();
int savedVersion = holder.metadata(path).get(VERSION, 0);
if (currentVersion == savedVersion) return;
if (currentVersion > savedVersion) { // This values updated.
value.setDefault(true); // Mark as default value, force write.
}
holder.metadata(path).set(VERSION, currentVersion);
});
} }
} }
+5 -11
View File
@@ -15,7 +15,7 @@
<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.1</version> <version>4.0.8</version>
<modules> <modules>
<module>core</module> <module>core</module>
<module>features/section</module> <module>features/section</module>
@@ -27,14 +27,14 @@
<module>providers/yaml</module> <module>providers/yaml</module>
<module>providers/gson</module> <module>providers/gson</module>
<!-- <module>providers/hocon</module>--> <!-- <module>providers/hocon</module>-->
<!-- <module>providers/sql</module>--> <module>providers/sql</module>
<!-- <module>providers/mongodb</module>--> <module>providers/mongodb</module>
<module>demo</module> <module>demo</module>
</modules> </modules>
<name>EasyConfiguration</name> <name>EasyConfiguration</name>
<description>轻松(做)配置,简单便捷的通用配置文件加载、读取与更新工具,可自定义配置格式。</description> <description>A simple, easy-to-use and universal solution for managing configuration files.</description>
<url>https://github.com/CarmJos/EasyConfiguration</url> <url>https://github.com/CarmJos/EasyConfiguration</url>
<developers> <developers>
@@ -89,12 +89,6 @@
<url>https://repo1.maven.org/maven2/</url> <url>https://repo1.maven.org/maven2/</url>
</repository> </repository>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/*</url>
</repository>
</repositories> </repositories>
<distributionManagement> <distributionManagement>
@@ -202,7 +196,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version> <version>3.14.0</version>
<configuration> <configuration>
<source>${project.jdk.version}</source> <source>${project.jdk.version}</source>
<target>${project.jdk.version}</target> <target>${project.jdk.version}</target>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.1</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -2,7 +2,7 @@ package cc.carm.lib.configuration.source.json;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource; import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.SourcedSection;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
@@ -14,17 +14,17 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
public class JSONSource extends FileConfigSource<MemorySection, Map<String, Object>, JSONSource> { public class JSONSource extends FileConfigSource<SourcedSection, Map<String, Object>, JSONSource> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder() public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting() .serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter( .registerTypeAdapter(
MemorySection.class, SourcedSection.class,
(JsonSerializer<MemorySection>) (src, t, c) -> c.serialize(src.data()) (JsonSerializer<SourcedSection>) (src, t, c) -> c.serialize(src.data())
).create(); ).create();
protected final @NotNull Gson gson; protected final @NotNull Gson gson;
protected @Nullable MemorySection rootSection; protected @Nullable SourcedSection rootSection;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder,
@NotNull File file, @Nullable String resourcePath) { @NotNull File file, @Nullable String resourcePath) {
@@ -48,7 +48,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
protected JSONSource self() { protected @NotNull JSONSource self() {
return this; return this;
} }
@@ -58,7 +58,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized"); return Objects.requireNonNull(this.rootSection, "Root section is not initialized");
} }
@@ -70,7 +70,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override @Override
protected void onReload() throws Exception { protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class)); Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class));
this.rootSection = MemorySection.root(this, data); this.rootSection = SourcedSection.root(this, data);
this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间 this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
} }
+18 -10
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.mongodb.version>5.3.1</deps.mongodb.version>
<log4j.version>2.24.3</log4j.version> <log4j.version>2.24.3</log4j.version>
</properties> </properties>
<artifactId>easyconfiguration-mongodb</artifactId> <artifactId>easyconfiguration-mongodb</artifactId>
@@ -30,7 +31,7 @@
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId> <artifactId>easyconfiguration-feature-section</artifactId>
<version>${project.parent.version}</version> <version>${project.parent.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -38,14 +39,7 @@
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>5.3.1</version> <version>${deps.mongodb.version}</version>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -69,6 +63,20 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -0,0 +1,75 @@
package cc.carm.lib.configuration.source.mongodb;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class MongoConfigFactory extends ConfigurationFactory<MongoSource, ConfigurationHolder<MongoSource>, MongoConfigFactory> {
public static MongoConfigFactory from(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
return new MongoConfigFactory(collectionSupplier);
}
public static MongoConfigFactory from(@NotNull MongoCollection<Document> collection) {
return from(() -> collection);
}
public static MongoConfigFactory from(@NotNull MongoDatabase database, @NotNull String collectionName) {
return from(() -> database.getCollection(collectionName));
}
protected @NotNull Supplier<MongoCollection<Document>> collectionSupplier;
protected @NotNull String namespace = "config";
public MongoConfigFactory(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
super();
this.collectionSupplier = collectionSupplier;
}
public MongoConfigFactory collection(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
this.collectionSupplier = collectionSupplier;
return this;
}
public MongoConfigFactory collection(@NotNull MongoCollection<Document> collection) {
return collection(() -> collection);
}
public MongoConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return this;
}
public MongoConfigFactory namespace(@NotNull Supplier<String> namespace) {
return namespace(namespace.get());
}
@Override
protected MongoConfigFactory self() {
return this;
}
@Override
public @NotNull ConfigurationHolder<MongoSource> build() {
MongoCollection<Document> collection = this.collectionSupplier.get();
if (collection == null) {
throw new IllegalStateException("Failed to get MongoCollection<Document> from supplier");
}
return new ConfigurationHolder<MongoSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull MongoSource source = new MongoSource(this, System.currentTimeMillis(), collection, namespace);
@Override
public @NotNull MongoSource config() {
return this.source;
}
};
}
}
@@ -1,4 +1,76 @@
package cc.carm.lib.configuration.source.mongodb; package cc.carm.lib.configuration.source.mongodb;
public class MongoSource { import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.ReplaceOptions;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects;
public class MongoSource extends ConfigureSource<SourcedSection, Map<String, Object>, MongoSource> {
protected final @NotNull MongoCollection<Document> collection;
protected final @NotNull String namespace;
protected SourcedSection rootSection;
protected MongoSource(@NotNull ConfigurationHolder<? extends MongoSource> holder, long lastUpdateMillis,
@NotNull MongoCollection<Document> collection, @NotNull String namespace) {
super(holder, lastUpdateMillis);
this.collection = collection;
this.namespace = namespace;
try {
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected @NotNull MongoSource self() {
return this;
}
@Override
public @NotNull Map<String, Object> original() {
return section().data();
}
@Override
public @NotNull SourcedSection section() {
return Objects.requireNonNull(rootSection, "RootSection is not initialized");
}
public @NotNull String namespace() {
return this.namespace;
}
public @NotNull MongoCollection<Document> collection() {
return this.collection;
}
@Override
public void save() throws Exception {
Map<String, Object> data = this.rootSection.rawMap();
if (data.isEmpty()) return; // Skip saving if empty
if (data.containsKey("_id") && data.size() == 1) return; // Skip saving if only contains _id
ReplaceOptions options = new ReplaceOptions().upsert(true);
Document storage = new Document(data).append("_id", this.namespace);
this.collection.replaceOne(new Document("_id", this.namespace), storage, options);
}
@Override
protected void onReload() throws Exception {
Document storage = this.collection.find(new Document("_id", this.namespace)).first();
if (storage == null) storage = new Document();
else storage.remove("_id"); // Remove _id
this.rootSection = SourcedSection.root(this, storage);
}
} }
@@ -0,0 +1,12 @@
package config;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
interface MongoConfig extends Configuration {
ConfiguredValue<String> HOST = ConfiguredValue.of("127.0.0.1");
ConfiguredValue<Integer> PORT = ConfiguredValue.of(27017);
ConfiguredValue<String> USERNAME = ConfiguredValue.of("minecraft");
ConfiguredValue<String> PASSWORD = ConfiguredValue.of("minecraft");
ConfiguredValue<String> DATABASE = ConfiguredValue.of("minecraft");
}
@@ -0,0 +1,57 @@
package config;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.mongodb.MongoConfigFactory;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import org.bson.UuidRepresentation;
import org.junit.Test;
import java.io.File;
public class MongoTest {
boolean local = false;
@Test
public void test() {
if (!local) return;
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/mongo.json")).build();
gsonHolder.initialize(MongoConfig.class);
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(
"mongodb://" + MongoConfig.HOST.resolve() + ":" + MongoConfig.PORT.resolve()
))
.credential(MongoCredential.createCredential(
MongoConfig.USERNAME.resolve(), MongoConfig.DATABASE.resolve(),
MongoConfig.PASSWORD.resolve().toCharArray()
))
.uuidRepresentation(UuidRepresentation.STANDARD)
.build();
MongoClient mongoClient = MongoClients.create(settings);
MongoDatabase mongoDatabase = mongoClient.getDatabase(MongoConfig.DATABASE.resolve());
ConfigurationHolder<?> mongoHolder = MongoConfigFactory
.from(mongoDatabase, "configs")
.namespace("my_plugin")
.build();
// Test the configuration
ConfigurationTest.testDemo(mongoHolder);
ConfigurationTest.testInner(mongoHolder);
ConfigurationTest.save(mongoHolder);
}
}
+10 -10
View File
@@ -6,16 +6,16 @@ SQL database implementation, support for MySQL or MariaDB.
```mysql ```mysql
CREATE TABLE IF NOT EXISTS conf CREATE TABLE IF NOT EXISTS conf
( (
`namespace` VARCHAR(32) NOT NULL, # 命名空间 (代表其属于谁,类似于单个配置文件地址的概念) `namespace` VARCHAR(32) NOT NULL, # 命名空间 (代表其属于谁,类似于单个配置文件地址的概念)
`path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath) `path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath)
`type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...) `type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...)
`value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式) `value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式)
`inline_comment` TEXT, # 配置项的用法,本质是行内注释 `inline_comment` TEXT comment 'usage', # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT, # 配置项的描述,本质是顶部注释 `header_comment` MEDIUMTEXT comment 'description', # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT, # 配置项的描述,本质是部注释 `footer_comment` MEDIUMTEXT comment 'example', # 配置项的参考,本质是部注释
`version` INT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本 `version` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间 `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`namespace`, `path`) PRIMARY KEY (`namespace`, `path`)
) ENGINE = InnoDB ) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4; DEFAULT CHARSET = utf8mb4;
+20 -5
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -15,7 +15,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<log4j.version>2.24.3</log4j.version> <deps.mysql-driver.version>8.0.33</deps.mysql-driver.version>
<deps.log4j.version>2.24.3</deps.log4j.version>
</properties> </properties>
<artifactId>easyconfiguration-sql</artifactId> <artifactId>easyconfiguration-sql</artifactId>
@@ -77,21 +78,35 @@
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> <artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${deps.mysql-driver.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@@ -0,0 +1,133 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
import cc.carm.lib.easysql.api.SQLManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.util.HashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SQLConfigFactory extends ConfigurationFactory<SQLSource, ConfigurationHolder<SQLSource>, SQLConfigFactory> {
public static SQLConfigFactory from(@NotNull Supplier<@NotNull SQLManager> managerSupplier) {
return new SQLConfigFactory(managerSupplier);
}
public static SQLConfigFactory from(@NotNull SQLManager manager) {
return from(() -> manager);
}
protected @NotNull Supplier<SQLManager> managerSupplier;
protected Supplier<Gson> gsonSupplier = () -> SQLSource.DEFAULT_GSON;
protected HashMap<Integer, SQLValueResolver<?>> resolvers = new HashMap<>(SQLValueResolver.STANDARD_RESOLVERS);
protected String tableName = "configs";
protected String namespace = "default";
public SQLConfigFactory(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
}
@Override
protected SQLConfigFactory self() {
return this;
}
public SQLConfigFactory manager(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
return self();
}
public SQLConfigFactory manager(@NotNull SQLManager manager) {
return manager(() -> manager);
}
public SQLConfigFactory gson(@NotNull Supplier<Gson> gsonSupplier) {
this.gsonSupplier = gsonSupplier;
return self();
}
public SQLConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder();
builder.accept(gsonBuilder);
return gsonBuilder.create();
});
}
public SQLConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public SQLConfigFactory resolver(@Range(from = 0, to = 255) int type, @NotNull SQLValueResolver<?> resolver) {
this.resolvers.put(type, resolver);
return self();
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser) {
return resolver(id, ValueType.of(clazz), parser);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser) {
return resolver(id, SQLValueResolver.of(type, parser));
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<T, String> serializer) {
return resolver(id, ValueType.of(clazz), parser, serializer);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<T, String> serializer) {
return resolver(id, SQLValueResolver.of(type, parser, serializer));
}
public SQLConfigFactory tableName(@NotNull String tableName) {
this.tableName = tableName;
return self();
}
public SQLConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return self();
}
@Override
public @NotNull ConfigurationHolder<SQLSource> build() {
Gson gson = gsonSupplier.get();
if (gson == null) throw new NullPointerException("No Gson instance provided.");
SQLManager manager = this.managerSupplier.get();
if (manager == null) throw new NullPointerException("No SQLManager instance provided.");
Commentable.registerMeta(this.initializer);
VersionedMetaTypes.register(this.initializer);
return new ConfigurationHolder<SQLSource>(this.adapters, this.options, this.metadata, this.initializer) {
final SQLSource source = new SQLSource(
this, System.currentTimeMillis(),
gson, manager, resolvers, tableName, namespace
);
@Override
public @NotNull SQLSource config() {
return source;
}
};
}
}
@@ -1,6 +1,12 @@
package cc.carm.lib.configuration.source.sql; package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface SQLOptions { public interface SQLOptions {
/**
* Whether to purge the configuration's in-database data when saving.
*/
ConfigurationOption<Boolean> PURGE = ConfigurationOption.of( true);
} }
@@ -1,53 +1,62 @@
package cc.carm.lib.configuration.source.sql; package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
import cc.carm.lib.easysql.api.SQLManager; import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.api.SQLQuery;
import cc.carm.lib.easysql.api.SQLTable; import cc.carm.lib.easysql.api.SQLTable;
import cc.carm.lib.easysql.api.enums.IndexType; import cc.carm.lib.easysql.api.enums.IndexType;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSource> { public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Object>, SQLSource> {
protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder() protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting() .serializeNulls().disableHtmlEscaping().create();
.create();
protected final @NotNull Gson gson; protected final @NotNull Gson gson;
protected final @NotNull SQLManager sqlManager; protected final @NotNull SQLManager sqlManager;
protected final @NotNull String namespace; protected final @NotNull String namespace;
protected final @NotNull SQLTable table; protected final @NotNull SQLTable table;
protected final @NotNull Set<String> updated = new HashSet<>(); protected final @NotNull Map<Integer, SQLValueResolver<?>> resolvers;
protected MemorySection rootSection; protected SourcedSection rootSection;
public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis, public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis,
@NotNull Gson gson, @NotNull SQLManager sqlManager, @NotNull String tableName, @NotNull String namespace) { @NotNull Gson gson, @NotNull SQLManager sqlManager,
@NotNull Map<Integer, SQLValueResolver<?>> resolvers,
@NotNull String tableName, @NotNull String namespace) {
super(holder, lastUpdateMillis); super(holder, lastUpdateMillis);
this.gson = gson; this.gson = gson;
this.sqlManager = sqlManager; this.sqlManager = sqlManager;
this.resolvers = resolvers;
this.namespace = namespace; this.namespace = namespace;
this.table = SQLTable.of(tableName, builder -> { this.table = SQLTable.of(tableName, builder -> {
builder.addColumn("namespace", "VARCHAR(32) NOT NULL"); builder.addColumn("namespace", "VARCHAR(32) NOT NULL");
builder.addColumn("path", "VARCHAR(96) NOT NULL"); builder.addColumn("path", "VARCHAR(96) NOT NULL");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("value", "TEXT"); builder.addColumn("value", "TEXT");
builder.addColumn("inline_comment", "TEXT"); builder.addColumn("inline_comment", "TEXT");
builder.addColumn("header_comments", "MEDIUMTEXT"); builder.addColumn("header_comments", "MEDIUMTEXT");
builder.addColumn("footer_comments", "MEDIUMTEXT"); builder.addColumn("footer_comments", "MEDIUMTEXT");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0"); builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0");
builder.addColumn("create_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"); builder.addColumn("create_time", "TIMESTAMP NOT NULL");
builder.addColumn("update_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"); builder.addColumn("update_time", "TIMESTAMP NOT NULL");
builder.setIndex( builder.setIndex(
IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(), IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(),
@@ -55,82 +64,134 @@ public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSour
); );
builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}); });
try {
this.table.create(this.sqlManager);
onReload();
} catch (Exception e) {
e.printStackTrace();
}
} }
@Override @Override
protected SQLSource self() { protected @NotNull SQLSource self() {
return this; return this;
} }
public @NotNull Gson gson() {
return gson;
}
@Override @Override
public @NotNull Map<?, ?> original() { public @NotNull Map<String, Object> original() {
return section().data(); return section().data();
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized."); return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
} }
public int purge() throws Exception {
return this.table.createDelete().addCondition("namespace", namespace).build().execute();
}
@Override @Override
public void save() throws Exception { public void save() throws Exception {
if (this.updated.isEmpty()) return; // Nothing to save LocalDateTime time = LocalDateTime.now(); // Update time
List<Object[]> dataValues = new ArrayList<>();
Date date = new Date(); // Update time SourcedSection section = section();
List<Object[]> values = new ArrayList<>(); Map<String, ConfigValue<?>> values = holder().registeredValues();
for (Map.Entry<String, ConfigValue<?>> entry : values.entrySet()) {
@NotNull String path = entry.getKey();
@NotNull ConfigValue<?> config = entry.getValue();
@Nullable Object value = section.get(path);
for (String path : this.updated) { if (value instanceof SourcedSection) {
Object value = get(path); value = ((SourcedSection) value).rawMap();
} else if (value instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) value) {
if (obj instanceof SourcedSection) {
list.add(((SourcedSection) obj).rawMap());
} else {
list.add(obj);
}
}
value = list;
}
// if (value instanceof SQLConfigWrapper) { if (value == null) continue;
// value = getSourceMap(((SQLConfigWrapper) value).getSource());
// } try {
// int typeID = typeIdOf(value);
// SQLValueResolver<?> type = SQLValueTypes.get(value.getClass()); String data = serialize(typeID, value);
// if (type != null) { if (data == null) continue;
// values.add(new Object[]{
// this.namespace, path, date, int version = holder().metadata(path).get(VersionedMetaTypes.VERSION, 0);
// type.getID(), type.serializeObject(value), dataValues.add(new Object[]{
// getComments().getInlineComment(path), namespace, path, time, version, typeID, data,
// GSON.toJson(getComments().getHeaderComment(path)) Commentable.getInlineComment(holder(), path),
// }); gson.toJson(Commentable.getHeaderComments(holder(), path)),
// } gson.toJson(Commentable.getFooterComments(holder(), path))
});
} catch (Exception ex) {
ex.printStackTrace();
}
} }
this.updated.clear(); if (holder.option(SQLOptions.PURGE)) {
purge();
}
this.table.createReplaceBatch() this.table.createReplaceBatch()
.setColumnNames("namespace", "path", "update_time", "type", "value", "inline_comment", "header_comments") .setColumnNames(
.setAllParams(values) "namespace", "path", "update_time", "version", "type", "value",
.execute(); "inline_comment", "header_comments", "footer_comments"
).setAllParams(dataValues).execute();
} }
@Override @Override
protected void onReload() throws Exception { protected void onReload() throws Exception {
LinkedHashMap<String, Object> values = new LinkedHashMap<>(); Map<String, Object> loaded = new LinkedHashMap<>();
try (SQLQuery query = this.table.createQuery()
// try (SQLQuery query = this.table.createQuery() .addCondition("namespace", namespace)
// .addCondition("namespace", namespace) .build().execute()) {
// .build().execute()) { ResultSet rs = query.getResultSet();
// ResultSet rs = query.getResultSet(); while (rs.next()) {
// while (rs.next()) { String path = rs.getString("path");
// String path = rs.getString("path"); if (path == null) continue; // Path should be not null
// int type = rs.getInt("type"); int ver = rs.getInt("version");
// try { try {
// SQLValueResolver<?> resolver = SQLValueTypes.get(type); loaded.put(path, parse(rs.getInt("type"), rs.getString("value")));
// if (resolver == null) throw new IllegalStateException("No resolver for type #" + type); if (ver != 0) holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
// String value = rs.getString("value"); } catch (Exception e) {
// values.put(path, resolver.resolve(value)); e.printStackTrace();
// }
// loadInlineComment(path, rs.getString("inline_comment")); }
// loadHeaderComment(path, rs.getString("header_comments")); }
// } catch (Exception ex) { this.rootSection = SourcedSection.root(this, loaded);
// ex.printStackTrace();
// }
// }
// }
//
// this.rootConfiguration = new SQLConfigWrapper(this, values);
} }
protected @Nullable Object parse(int type, String value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.resolve(this, value);
}
protected @Nullable String serialize(int type, @NotNull Object value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.serialize(this, value);
}
protected int typeIdOf(@NotNull Object value) {
return this.resolvers.entrySet().stream()
.filter(entry -> entry.getValue().isInstance(value))
.findFirst().map(Map.Entry::getKey)
.orElseThrow(() -> new IllegalStateException("No resolvers for value " + value.getClass().getName()));
}
} }
@@ -0,0 +1,115 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.DataFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class SQLValueResolver<T> {
public static final @NotNull SQLValueResolver<String> STRING = SQLValueResolver.of(ValueType.STRING, s -> s);
public static final @NotNull SQLValueResolver<Byte> BYTE = SQLValueResolver.of(ValueType.BYTE, Byte::parseByte);
public static final @NotNull SQLValueResolver<Short> SHORT = SQLValueResolver.of(ValueType.SHORT, Short::parseShort);
public static final @NotNull SQLValueResolver<Integer> INTEGER = SQLValueResolver.of(ValueType.INTEGER, Integer::parseInt);
public static final @NotNull SQLValueResolver<Long> LONG = SQLValueResolver.of(ValueType.LONG, Long::parseLong);
public static final @NotNull SQLValueResolver<Float> FLOAT = SQLValueResolver.of(ValueType.FLOAT, Float::parseFloat);
public static final @NotNull SQLValueResolver<Double> DOUBLE = SQLValueResolver.of(ValueType.DOUBLE, Double::parseDouble);
public static final @NotNull SQLValueResolver<Boolean> BOOLEAN = SQLValueResolver.of(ValueType.BOOLEAN, Boolean::parseBoolean);
public static final @NotNull SQLValueResolver<Character> CHAR = SQLValueResolver.of(ValueType.CHAR, s -> s.charAt(0));
public static final @NotNull SQLValueResolver<List<?>> LIST = new SQLValueResolver<List<?>>(new ValueType<List<?>>() {
}) {
@Override
public @Nullable List<?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, List.class);
}
@Override
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
public static final @NotNull SQLValueResolver<Map<?, ?>> MAP = new SQLValueResolver<Map<?, ?>>(new ValueType<Map<?, ?>>() {
}) {
@Override
public @Nullable Map<?, ?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, LinkedHashMap.class);
}
@Override
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
public static final @NotNull Map<Integer, SQLValueResolver<?>> STANDARD_RESOLVERS = Collections.unmodifiableMap(standards());
static Map<Integer, SQLValueResolver<?>> standards() {
Map<Integer, SQLValueResolver<?>> map = new LinkedHashMap<>();
map.put(0, STRING);
map.put(1, BYTE);
map.put(2, SHORT);
map.put(3, INTEGER);
map.put(4, LONG);
map.put(5, FLOAT);
map.put(6, DOUBLE);
map.put(7, BOOLEAN);
map.put(8, CHAR);
map.put(10, LIST);
map.put(11, MAP);
return map;
}
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type, @NotNull DataFunction<String, V> resolver) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull SQLSource source, String data) throws Exception {
return resolver.handle(data);
}
};
}
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type,
@NotNull DataFunction<String, V> resolver,
@NotNull DataFunction<V, String> serializer) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull SQLSource source, String data) throws Exception {
return resolver.handle(data);
}
@Override
public @NotNull String serialize(@NotNull SQLSource source, Object value) throws Exception {
return serializer.handle(type.cast(value));
}
};
}
protected final @NotNull ValueType<T> type;
protected SQLValueResolver(@NotNull ValueType<T> type) {
this.type = type;
}
public @NotNull ValueType<T> getType() {
return type;
}
public boolean isInstance(@NotNull Object obj) {
return getType().isInstance(obj);
}
public abstract @Nullable T resolve(@NotNull SQLSource source, String data) throws Exception;
public @Nullable String serialize(@NotNull SQLSource source, Object value) throws Exception {
return String.valueOf(value);
}
}
@@ -1,27 +1,48 @@
package config; package config;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.sql.SQLConfigFactory;
import cc.carm.lib.easysql.EasySQL; import cc.carm.lib.easysql.EasySQL;
import cc.carm.lib.easysql.api.SQLManager; import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.beecp.BeeDataSourceConfig; import cc.carm.lib.easysql.beecp.BeeDataSourceConfig;
import org.junit.Test;
import java.io.File;
public class SQLConfigTest { public class SQLConfigTest {
boolean local = false;
@Test
public void test() { public void test() {
if (!local) return;
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/sql.json")).build();
gsonHolder.initialize(DatabaseConfiguration.class);
BeeDataSourceConfig config = new BeeDataSourceConfig(); BeeDataSourceConfig config = new BeeDataSourceConfig();
config.setDriverClassName("org.h2.Driver"); config.setDriverClassName(DatabaseConfiguration.DRIVER_NAME.resolve());
config.setJdbcUrl("jdbc:h2:file:target/test;DB_CLOSE_DELAY=-1;MODE=MySQL;"); config.setJdbcUrl(DatabaseConfiguration.buildJDBC());
config.setUsername(DatabaseConfiguration.USERNAME.resolve());
config.setPassword(DatabaseConfiguration.PASSWORD.resolve());
SQLManager manager = EasySQL.createManager(config); SQLManager manager = EasySQL.createManager(config);
manager.setDebugMode(true); manager.setDebugMode(true);
// SQLConfigProvider provider = EasyConfiguration.from(manager, "conf_test", "TESTING"); ConfigurationHolder<?> holder = SQLConfigFactory.from(manager)
// .tableName("test_configs")
// ConfigurationTest.testDemo(provider); .namespace("testing")
// ConfigurationTest.testInner(provider); .build();
//
// ConfigurationTest.save(provider); ConfigurationTest.testDemo(holder);
// ConfigurationTest.testInner(holder);
// EasySQL.shutdownManager(manager);
ConfigurationTest.save(holder);
EasySQL.shutdownManager(manager);
} }
} }
-71
View File
@@ -62,74 +62,3 @@ dependencies {
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
} }
``` ```
## Example file format
```yaml
version: 1.0.0
test-save: false
test-number: 6161926779561752576
test-enum: DAYS
# Section类型数据测试
user: # Section数据也支持InlineComment注释
name: '35882'
info:
uuid: 554b79f1-7c39-4960-82d1-5514c9734417
uuid-value: 9a86663e-2fc7-4851-a423-c7e5d8e94a47 # This is an inline comment
sub:
that:
operators: []
# [ID - UUID]对照表
# 用于测试Map类型的解析与序列化保存
users:
'1': 1c055bdd-c9d1-4931-8270-3d162247f38a
'2': 934e2b05-2417-424e-80fd-fe58c6725837
'3': 442949a2-8345-4210-a87b-593d7168980e
'4': 5c015453-4b5b-42e3-ad87-b9498f2dfeab
'5': 8f9640e7-0fbd-4f73-b737-f0b707215e71
# Inner Test
inner:
inner-value: 51.223503560658166
class-value: 1.0
test:
# Section类型数据测试
user: # Section数据也支持InlineComment注释
name: Carm
info:
uuid: 3d1ef2a0-a38b-44f3-b15f-8e3b22cb8cc6
# 以下内容用于测试序列化
model-test:
some-model:
==: SomeModel
num: 855
name: 4f6b7
any-model:
==: AnyModel
name: 63d05
state: false
models:
- name: 481f3
state: true
- name: fcf3e
state: false
- name: '14e50'
state: false
model-map:
a:
name: 1fb9b
state: false
b:
name: 5486f
state: false
```
+1 -8
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.1</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -29,13 +29,6 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>yamlcommentwriter</artifactId>
<version>1.2.0</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-file</artifactId> <artifactId>easyconfiguration-feature-file</artifactId>
@@ -4,9 +4,8 @@ import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.commentable.CommentableOptions; import cc.carm.lib.configuration.commentable.CommentableOptions;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource; import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.option.StandardOptions;
import cc.carm.lib.configuration.source.section.ConfigureSection; import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.yamlcommentupdater.CommentedSection; import cc.carm.lib.yamlcommentupdater.CommentedSection;
import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter; import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -27,14 +26,14 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
public class YAMLSource public class YAMLSource
extends FileConfigSource<MemorySection, Map<String, Object>, YAMLSource> extends FileConfigSource<SourcedSection, Map<String, Object>, YAMLSource>
implements CommentedSection { implements CommentedSection {
protected final @NotNull YamlConstructor yamlConstructor; protected final @NotNull YamlConstructor yamlConstructor;
protected final @NotNull YamlRepresenter yamlRepresenter; protected final @NotNull YamlRepresenter yamlRepresenter;
protected final @NotNull Yaml yaml; protected final @NotNull Yaml yaml;
protected @Nullable MemorySection rootSection; protected @Nullable SourcedSection rootSection;
protected YAMLSource(@NotNull ConfigurationHolder<? extends YAMLSource> holder, protected YAMLSource(@NotNull ConfigurationHolder<? extends YAMLSource> holder,
@NotNull File file, @Nullable String resourcePath) { @NotNull File file, @Nullable String resourcePath) {
@@ -56,8 +55,8 @@ public class YAMLSource
} }
@Override @Override
protected YAMLSource self() { protected @NotNull YAMLSource self() {
return null; return this;
} }
@Override @Override
@@ -66,15 +65,10 @@ public class YAMLSource
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized."); return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
} }
@Override
public char separator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public @NotNull LoaderOptions loaderOptions() { public @NotNull LoaderOptions loaderOptions() {
return holder().options().get(YAMLOptions.LOADER); return holder().options().get(YAMLOptions.LOADER);
} }
@@ -115,9 +109,9 @@ public class YAMLSource
@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)));
@@ -136,18 +130,18 @@ public class YAMLSource
return this.saveToString(section()); return this.saveToString(section());
} }
public @NotNull MemorySection loadFromString(@NotNull String data) throws Exception { public @NotNull SourcedSection loadFromString(@NotNull String data) throws Exception {
MappingNode mappingNode; MappingNode mappingNode;
try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) { try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) {
Node rawNode = this.yaml.compose(reader); Node rawNode = this.yaml.compose(reader);
mappingNode = (MappingNode) rawNode; mappingNode = (MappingNode) rawNode;
} }
if (mappingNode == null) return MemorySection.root(this); if (mappingNode == null) return SourcedSection.root(this);
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
this.constructMap(mappingNode, map); this.constructMap(mappingNode, map);
return MemorySection.root(this, map); return SourcedSection.root(this, map);
} }
private void constructMap(@NotNull MappingNode mappingNode, @NotNull Map<String, Object> section) { private void constructMap(@NotNull MappingNode mappingNode, @NotNull Map<String, Object> section) {
@@ -176,7 +170,7 @@ public class YAMLSource
public String serializeValue(@NotNull String key, @NotNull Object value) { public String serializeValue(@NotNull String key, @NotNull Object value) {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
map.put(key, value); map.put(key, value);
return saveToString(MemorySection.root(this, map)); return saveToString(SourcedSection.root(this, map));
} }
@Override @Override
@@ -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.
@@ -9,7 +9,6 @@ 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 {