diff --git a/core/pom.xml b/core/pom.xml
index 5f9a61e..aa4ad15 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -5,7 +5,7 @@
easyconfiguration-parent
cc.carm.lib
- 3.9.1
+ 4.0.0
4.0.0
diff --git a/demo/pom.xml b/demo/pom.xml
index 13700da..b2a1fc9 100644
--- a/demo/pom.xml
+++ b/demo/pom.xml
@@ -5,7 +5,7 @@
easyconfiguration-parent
cc.carm.lib
- 3.9.1
+ 4.0.0
4.0.0
diff --git a/features/commentable/pom.xml b/features/commentable/pom.xml
index 5aee317..6b056ea 100644
--- a/features/commentable/pom.xml
+++ b/features/commentable/pom.xml
@@ -6,7 +6,7 @@
cc.carm.lib
easyconfiguration-parent
- 3.9.1
+ 4.0.0
../../pom.xml
diff --git a/features/file/pom.xml b/features/file/pom.xml
index 861bcb1..1ebb3f8 100644
--- a/features/file/pom.xml
+++ b/features/file/pom.xml
@@ -6,7 +6,7 @@
cc.carm.lib
easyconfiguration-parent
- 3.9.1
+ 4.0.0
../../pom.xml
diff --git a/features/section/pom.xml b/features/section/pom.xml
index 9a11655..192790c 100644
--- a/features/section/pom.xml
+++ b/features/section/pom.xml
@@ -6,7 +6,7 @@
cc.carm.lib
easyconfiguration-parent
- 3.9.1
+ 4.0.0
../../pom.xml
diff --git a/features/versioned/pom.xml b/features/versioned/pom.xml
index 1b40382..3c6f09d 100644
--- a/features/versioned/pom.xml
+++ b/features/versioned/pom.xml
@@ -6,7 +6,7 @@
cc.carm.lib
easyconfiguration-parent
- 3.9.1
+ 4.0.0
../../pom.xml
diff --git a/pom.xml b/pom.xml
index a09011e..05bae52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
cc.carm.lib
easyconfiguration-parent
pom
- 3.9.1
+ 4.0.0
core
features/section
diff --git a/providers/gson/pom.xml b/providers/gson/pom.xml
index 0732fe1..405f0fd 100644
--- a/providers/gson/pom.xml
+++ b/providers/gson/pom.xml
@@ -5,7 +5,7 @@
easyconfiguration-parent
cc.carm.lib
- 3.9.1
+ 4.0.0
../../pom.xml
4.0.0
diff --git a/providers/gson/src/main/java/cc/carm/lib/configuration/source/json/JSONConfigFactory.java b/providers/gson/src/main/java/cc/carm/lib/configuration/source/json/JSONConfigFactory.java
index f286c01..c4e488b 100644
--- a/providers/gson/src/main/java/cc/carm/lib/configuration/source/json/JSONConfigFactory.java
+++ b/providers/gson/src/main/java/cc/carm/lib/configuration/source/json/JSONConfigFactory.java
@@ -7,6 +7,7 @@ import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.File;
+import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -57,7 +58,7 @@ public class JSONConfigFactory extends FileConfigFactory(this.adapters, this.options, new ConcurrentHashMap<>(), this.initializer) {
+ return new ConfigurationHolder(this.adapters, this.options, new HashMap<>(), this.initializer) {
final JSONSource source = new JSONSource(this, configFile, sourcePath, gson);
@Override
diff --git a/providers/yaml/pom.xml b/providers/yaml/pom.xml
index f1bd5ed..3e1239f 100644
--- a/providers/yaml/pom.xml
+++ b/providers/yaml/pom.xml
@@ -2,13 +2,13 @@
+ 4.0.0
easyconfiguration-parent
cc.carm.lib
- 3.9.1
+ 4.0.0
../../pom.xml
- 4.0.0
${project.jdk.version}
${project.jdk.version}
diff --git a/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLConfigFactory.java b/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLConfigFactory.java
index 5458631..5d063aa 100644
--- a/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLConfigFactory.java
+++ b/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLConfigFactory.java
@@ -1,4 +1,84 @@
package cc.carm.lib.configuration.source.yaml;
-public class YAMLConfigFactory {
+import cc.carm.lib.configuration.commentable.CommentableMetaTypes;
+import cc.carm.lib.configuration.source.ConfigurationHolder;
+import cc.carm.lib.configuration.source.file.FileConfigFactory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.LoaderOptions;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class YAMLConfigFactory extends FileConfigFactory, YAMLConfigFactory> {
+
+ public static YAMLConfigFactory from(@NotNull String path) {
+ return new YAMLConfigFactory(new File(path));
+ }
+
+ public static YAMLConfigFactory from(@NotNull File file) {
+ return new YAMLConfigFactory(file);
+ }
+
+ public static YAMLConfigFactory from(@NotNull File parent, @NotNull String configName) {
+ return new YAMLConfigFactory(new File(parent, configName));
+ }
+
+ public YAMLConfigFactory(@NotNull File file) {
+ super(file);
+ }
+
+ @Override
+ protected YAMLConfigFactory self() {
+ return this;
+ }
+
+ public YAMLConfigFactory loader(@NotNull LoaderOptions loaderOptions) {
+ return option(YAMLOptions.LOADER, loaderOptions);
+ }
+
+ public YAMLConfigFactory loader(@NotNull Consumer modifier) {
+ return option(YAMLOptions.LOADER, modifier);
+ }
+
+ public YAMLConfigFactory indent(@Range(from = 2, to = 8) int indent) {
+ return dumper(d -> d.setIndent(indent));
+ }
+
+ public YAMLConfigFactory width(@Range(from = 8, to = 1000) int width) {
+ return dumper(d -> d.setWidth(width));
+ }
+
+ public YAMLConfigFactory dumper(@NotNull DumperOptions dumperOptions) {
+ return option(YAMLOptions.DUMPER, dumperOptions);
+ }
+
+ public YAMLConfigFactory dumper(@NotNull Consumer modifier) {
+ return option(YAMLOptions.DUMPER, modifier);
+ }
+
+ @Override
+ public @NotNull ConfigurationHolder build() {
+
+ File configFile = this.file;
+ String sourcePath = this.resourcePath;
+
+ CommentableMetaTypes.register(this.initializer); // Register commentable meta types
+
+ return new ConfigurationHolder(
+ this.adapters, this.options, new HashMap<>(), this.initializer
+ ) {
+ final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath);
+
+ @Override
+ public @NotNull YAMLSource config() {
+ return this.source;
+ }
+ };
+ }
+
+
}
diff --git a/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLOptions.java b/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLOptions.java
new file mode 100644
index 0000000..e061a9e
--- /dev/null
+++ b/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLOptions.java
@@ -0,0 +1,27 @@
+package cc.carm.lib.configuration.source.yaml;
+
+import cc.carm.lib.configuration.source.option.ConfigurationOption;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.LoaderOptions;
+
+public interface YAMLOptions {
+
+
+ ConfigurationOption LOADER = ConfigurationOption.of(() -> {
+ LoaderOptions loaderOptions = new LoaderOptions();
+ loaderOptions.setMaxAliasesForCollections(100); // 100 aliases
+ loaderOptions.setCodePointLimit(5 * 1024 * 1024); // 5MB
+ return loaderOptions;
+ });
+
+ ConfigurationOption DUMPER = ConfigurationOption.of(() -> {
+ DumperOptions options = new DumperOptions();
+ options.setIndent(2);
+ options.setWidth(120);
+ options.setProcessComments(true);
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ return options;
+ });
+
+
+}
diff --git a/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLSource.java b/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLSource.java
index d7af585..054062d 100644
--- a/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLSource.java
+++ b/providers/yaml/src/main/java/cc/carm/lib/configuration/source/yaml/YAMLSource.java
@@ -1,4 +1,222 @@
package cc.carm.lib.configuration.source.yaml;
-public class YAMLSource {
+import cc.carm.lib.configuration.commentable.CommentableMetaTypes;
+import cc.carm.lib.configuration.source.ConfigurationHolder;
+import cc.carm.lib.configuration.source.file.FileConfigSource;
+import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
+import cc.carm.lib.configuration.source.section.ConfigureSection;
+import cc.carm.lib.configuration.source.section.MemorySection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.comments.CommentLine;
+import org.yaml.snakeyaml.comments.CommentType;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+import org.yaml.snakeyaml.nodes.*;
+import org.yaml.snakeyaml.reader.UnicodeReader;
+import org.yaml.snakeyaml.representer.Representer;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class YAMLSource extends FileConfigSource, YAMLSource> {
+
+ protected final @NotNull YamlConstructor yamlConstructor;
+ protected final @NotNull YamlRepresenter yamlRepresenter;
+ protected final @NotNull Yaml yaml;
+
+ protected @Nullable MemorySection rootSection;
+
+ protected YAMLSource(@NotNull ConfigurationHolder extends YAMLSource> holder,
+ @NotNull File file, @Nullable String resourcePath) {
+ super(holder, 0, file, resourcePath);
+ this.yamlConstructor = new YamlConstructor(loaderOptions());
+ this.yamlRepresenter = new YamlRepresenter(dumperOptions());
+ this.yaml = new Yaml(this.yamlConstructor, this.yamlRepresenter, dumperOptions());
+
+ initialize();
+ }
+
+ public void initialize() {
+ try {
+ initializeFile();
+ onReload();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected YAMLSource self() {
+ return null;
+ }
+
+ @Override
+ public @NotNull Map, ?> original() {
+ return section().data();
+ }
+
+ @Override
+ public @NotNull MemorySection section() {
+ return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
+ }
+
+ public @NotNull LoaderOptions loaderOptions() {
+ return holder().options().get(YAMLOptions.LOADER);
+ }
+
+ public @NotNull DumperOptions dumperOptions() {
+ return holder().options().get(YAMLOptions.DUMPER);
+ }
+
+ @NotNull
+ private MappingNode toNodeTree(@NotNull final ConfigureSection section) {
+ List nodeTuples = new ArrayList<>();
+ for (final Map.Entry entry : section.getValues(false).entrySet()) {
+
+ final Node keyNode = this.yaml.represent(entry.getKey());
+ final Node valueNode;
+ if (entry.getValue() instanceof ConfigureSection) {
+ valueNode = this.toNodeTree((ConfigureSection) entry.getValue());
+ } else {
+ valueNode = this.yaml.represent(entry.getValue());
+ }
+
+ keyNode.setBlockComments(buildComments(CommentType.BLOCK, CommentableMetaTypes.HEADER_COMMENTS, entry.getKey()));
+ if (valueNode instanceof MappingNode || valueNode instanceof SequenceNode) {
+ keyNode.setInLineComments(buildComment(CommentType.IN_LINE, CommentableMetaTypes.INLINE_COMMENT, entry.getKey()));
+ } else {
+ valueNode.setInLineComments(buildComment(CommentType.IN_LINE, CommentableMetaTypes.INLINE_COMMENT, entry.getKey()));
+ }
+// keyNode.setEndComments(buildComments(CommentType.BLOCK, CommentableMetaTypes.FOOTER_COMMENTS, entry.getKey()));
+
+ nodeTuples.add(new NodeTuple(keyNode, valueNode));
+ }
+
+ return new MappingNode(Tag.MAP, nodeTuples, DumperOptions.FlowStyle.BLOCK);
+ }
+
+ public List buildComments(@NotNull CommentType type, @NotNull ConfigurationMetadata> meta,
+ @Nullable String path) {
+ List comments = holder.metadata(path).get(meta);
+ if (comments == null) return Collections.emptyList();
+ return comments.stream().map(s -> {
+ if (s.isEmpty()) return new CommentLine(null, null, "", CommentType.BLANK_LINE);
+ else return new CommentLine(null, null, s, type);
+ }).collect(Collectors.toList());
+ }
+
+ public List buildComment(@NotNull CommentType type, @NotNull ConfigurationMetadata meta,
+ @Nullable String path) {
+ String comment = holder.metadata(path).get(meta);
+ if (comment == null || comment.isEmpty()) return Collections.emptyList();
+ return Collections.singletonList(new CommentLine(null, null, comment, type));
+ }
+
+
+ @NotNull
+ public String saveToString() {
+
+ MappingNode mappingNode = this.toNodeTree(this);
+// mappingNode.setBlockComments(this.getCommentLines(this.saveHeader(this.getOptions().getHeader()), CommentType.BLOCK));
+// mappingNode.setEndComments(this.getCommentLines(this.getOptions().getFooter(), CommentType.BLOCK));
+
+ StringWriter writer = new StringWriter();
+ if ((mappingNode.getBlockComments() == null || mappingNode.getBlockComments().isEmpty())
+ && (mappingNode.getEndComments() == null || mappingNode.getEndComments().isEmpty())
+ && (mappingNode.getInLineComments() == null || mappingNode.getInLineComments().isEmpty())
+ && mappingNode.getValue().isEmpty()) {
+ writer.write("");
+ } else {
+ if (mappingNode.getValue().isEmpty()) {
+ mappingNode.setFlowStyle(DumperOptions.FlowStyle.FLOW);
+ }
+ this.yaml.serialize(mappingNode, writer);
+ }
+
+ return writer.toString();
+ }
+
+ @Override
+ public void save() throws Exception {
+ fileWriter(w -> w.write(saveToString()));
+ }
+
+ @Override
+ protected void onReload() throws Exception {
+ this.rootSection = fileReadString(this::loadFromString);
+ }
+
+ public @NotNull MemorySection loadFromString(@NotNull String data) throws Exception {
+ MappingNode mappingNode;
+ try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) {
+ Node rawNode = this.yaml.compose(reader);
+ mappingNode = (MappingNode) rawNode;
+ }
+ if (mappingNode == null) return MemorySection.root(this);
+
+ Map map = new LinkedHashMap<>();
+ this.constructMap(mappingNode, map);
+ return MemorySection.root(this, map);
+ }
+
+ private void constructMap(@NotNull MappingNode mappingNode, @NotNull Map section) {
+ this.yamlConstructor.flattenMapping(mappingNode);
+ for (final NodeTuple nodeTuple : mappingNode.getValue()) {
+
+ final Node keyNode = nodeTuple.getKeyNode();
+ final String key = String.valueOf(this.yamlConstructor.construct(keyNode));
+ Node valueNode = nodeTuple.getValueNode();
+
+ while (valueNode instanceof AnchorNode) {
+ valueNode = ((AnchorNode) valueNode).getRealNode();
+ }
+
+ if (valueNode instanceof MappingNode) {
+ Map child = new LinkedHashMap<>();
+ this.constructMap((MappingNode) valueNode, child);
+ section.put(key, child);
+ } else {
+ section.put(key, this.yamlConstructor.construct(valueNode));
+ }
+ }
+ }
+
+ public static class YamlRepresenter extends Representer {
+
+ public YamlRepresenter(@NotNull final DumperOptions dumperOptions) {
+ super(dumperOptions);
+ this.multiRepresenters.put(ConfigureSection.class, new RepresentMap() {
+ @NotNull
+ @Override
+ public Node representData(@NotNull final Object object) {
+ return super.representData(((ConfigureSection) object).getValues(false));
+ }
+ });
+ this.multiRepresenters.remove(Enum.class);
+ }
+ }
+
+ public static class YamlConstructor extends SafeConstructor {
+
+ public YamlConstructor(@NotNull final LoaderOptions loaderOptions) {
+ super(loaderOptions);
+ }
+
+ @Nullable
+ Object construct(@NotNull final Node node) {
+ return this.constructObject(node);
+ }
+
+ @Override
+ protected void flattenMapping(@NotNull final MappingNode mappingNode) {
+ super.flattenMapping(mappingNode);
+ }
+ }
+
+
}