diff --git a/src/main/java/me/blvckbytes/bbconfigmapper/YamlConfig.java b/src/main/java/me/blvckbytes/bbconfigmapper/YamlConfig.java index dc5ee09..cf5b962 100644 --- a/src/main/java/me/blvckbytes/bbconfigmapper/YamlConfig.java +++ b/src/main/java/me/blvckbytes/bbconfigmapper/YamlConfig.java @@ -47,6 +47,29 @@ public class YamlConfig implements IConfig { + private static class LocateNodeResult { + private final @Nullable Node node; + private final boolean markedForExpressions; + private final Stack containerStack; + + private LocateNodeResult( + @Nullable Node node, + boolean markedForExpressions, + Stack containerStack + ) { + this.node = node; + this.markedForExpressions = markedForExpressions; + this.containerStack = containerStack; + } + + @Nullable MappingNode getLastContainer() { + if (containerStack.isEmpty()) + return null; + + return containerStack.pop(); + } + } + /* TODO: Add more debug logging calls to capture all details */ @@ -309,7 +332,7 @@ private boolean isKeyCommentedOut(String key, Node container) { // For lists, on the ScalarNode-s of items // If a comment is at the end of the file, it'll be an EndComment on the root MappingNode - Predicate commentedKeyMatcher = (comment) -> comment.getValue().trim().startsWith(key + ": "); + Predicate commentedKeyMatcher = (comment) -> comment.getValue().trim().startsWith(key + ":"); // Possibly, a key which is not the last has been commented out if (container instanceof MappingNode) { @@ -368,15 +391,11 @@ public int extendMissingKeys(YamlConfig other) { return false; String key = ((ScalarNode) tuple.getKeyNode()).getValue(); - int lastPathDotIndex = pathOfTuple.lastIndexOf('.'); - - if (lastPathDotIndex > 0) { - String parentPath = pathOfTuple.substring(0, lastPathDotIndex); - Node parentNode = locateNode(parentPath, false, false).a; + LocateNodeResult locateResult = locateNode(pathOfTuple, true, false); + Node parentNode = locateResult.getLastContainer(); - if (isKeyCommentedOut(key, parentNode)) - return false; - } + if (parentNode != null && isKeyCommentedOut(key, parentNode)) + return false; MappingNode container = locateContainerNode(pathOfTuple, true).a; List containerTuples = container.getValue(); @@ -433,8 +452,8 @@ private int forEachKeyPathRecursively(MappingNode node, @Nullable String parentP public @Nullable Object get(@Nullable String path) { logger.log(Level.FINEST, () -> DebugLogSource.YAML + "Object at path=" + path + " has been requested"); - Tuple<@Nullable Node, Boolean> target = locateNode(path, false, false); - Object value = target.a == null ? null : unwrapNode(target.a, target.b); + LocateNodeResult target = locateNode(path, false, false); + Object value = target.node == null ? null : unwrapNode(target.node, target.markedForExpressions); logger.log(Level.FINEST, () -> DebugLogSource.YAML + "Returning content of path=" + path + " with value=" + value); @@ -487,7 +506,7 @@ public boolean exists(@Nullable String path) { // For a key to exist, it's path has to exist within the // config, even if it points at a null value - boolean exists = locateNode(path, true, false).a != null; + boolean exists = locateNode(path, true, false).node != null; logger.log(Level.FINEST, () -> DebugLogSource.YAML + "Returning existence value for path=" + path + " of exists=" + exists); @@ -498,7 +517,7 @@ public boolean exists(@Nullable String path) { public void attachComment(@Nullable String path, List lines, boolean self) { logger.log(Level.FINEST, () -> DebugLogSource.YAML + "Attaching a comment to path=" + path + " (self=" + self + ") of lines=" + lines + " has been requested"); - Node target = locateNode(path, self, false).a; + Node target = locateNode(path, self, false).node; if (target == null) throw new IllegalStateException("Cannot attach a comment to a non-existing path"); @@ -517,7 +536,7 @@ public void attachComment(@Nullable String path, List lines, boolean sel public @Nullable List readComment(@Nullable String path, boolean self) { logger.log(Level.FINEST, () -> DebugLogSource.YAML + "Reading the comment at path=" + path + " (self=" + self + ") has been requested"); - Node target = locateNode(path, self, false).a; + Node target = locateNode(path, self, false).node; if (target == null) return null; @@ -554,7 +573,7 @@ private Tuple locateContainerNode(String keyPath, boolean f // Look up the container by the path provided before the last dot (in force mapping creation mode) else { keyPart = keyPath.substring(lastDotIndex + 1); - container = (MappingNode) locateNode(keyPath.substring(0, lastDotIndex), false, forceCreateMappings).a; + container = (MappingNode) locateNode(keyPath.substring(0, lastDotIndex), false, forceCreateMappings).node; } if (container == null || StringUtils.isBlank(keyPart)) @@ -690,9 +709,9 @@ private NodeTuple createNewTuple(@Nullable Node keyNode, @Nullable String key, N * @return A tuple of the target node or null if the target node didn't exist * as well as a boolean marking whether this path was marked for expressions */ - private @NotNull Tuple<@Nullable Node, Boolean> locateNode(@Nullable String path, boolean self, boolean forceCreateMappings) { + private @NotNull LocateNodeResult locateNode(@Nullable String path, boolean self, boolean forceCreateMappings) { if (path == null) - return new Tuple<>(rootNode, false); + return new LocateNodeResult(rootNode, false, null); // Keys should never contain any whitespace path = path.trim(); @@ -702,6 +721,7 @@ private NodeTuple createNewTuple(@Nullable Node keyNode, @Nullable String key, N Node node = rootNode; boolean markedForExpressions = false; + Stack containerStack = new Stack<>(); int endIndex = path.indexOf('.'), beginIndex = 0; @@ -716,9 +736,12 @@ private NodeTuple createNewTuple(@Nullable Node keyNode, @Nullable String key, N // Not a mapping node, cannot look up a path-part, the key has to be invalid if (!(node instanceof MappingNode)) - return new Tuple<>(null, markedForExpressions); + return new LocateNodeResult(null, markedForExpressions, containerStack); MappingNode mapping = (MappingNode) node; + + containerStack.push(mapping); + NodeTuple keyValueTuple = locateKey(mapping, pathPart); boolean markedAlready = expressionMarkerSuffix != null && pathPart.endsWith(expressionMarkerSuffix); @@ -750,7 +773,7 @@ private NodeTuple createNewTuple(@Nullable Node keyNode, @Nullable String key, N // Current path-part does not exist if (keyValueTuple == null) - return new Tuple<>(null, markedForExpressions); + return new LocateNodeResult(null, markedForExpressions, containerStack); // On the last iteration and the key itself has been requested if (endIndex == path.length() && self) @@ -770,7 +793,7 @@ private NodeTuple createNewTuple(@Nullable Node keyNode, @Nullable String key, N break; } - return new Tuple<>(node, markedForExpressions); + return new LocateNodeResult(node, markedForExpressions, containerStack); } /** diff --git a/src/main/java/me/blvckbytes/bbconfigmapper/sections/AConfigSection.java b/src/main/java/me/blvckbytes/bbconfigmapper/sections/AConfigSection.java index 0337078..f9c530a 100644 --- a/src/main/java/me/blvckbytes/bbconfigmapper/sections/AConfigSection.java +++ b/src/main/java/me/blvckbytes/bbconfigmapper/sections/AConfigSection.java @@ -28,13 +28,27 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; -import java.util.List; +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; public abstract class AConfigSection { + private static class DefaultSupplier { + private final Supplier supplier; + private final Set fieldExceptions; + + public DefaultSupplier(Supplier supplier, Set fieldExceptions) { + this.supplier = supplier; + this.fieldExceptions = fieldExceptions; + } + } + private final EvaluationEnvironmentBuilder baseEnvironment; + private final Map, DefaultSupplier> fieldDefaultSuppliers; public AConfigSection(EvaluationEnvironmentBuilder baseEnvironment) { + this.fieldDefaultSuppliers = new HashMap<>(); this.baseEnvironment = baseEnvironment; } @@ -60,7 +74,7 @@ public EvaluationEnvironmentBuilder getBaseEnvironment() { * @param field Target field * @return Value to use as a default */ - public @Nullable Object defaultFor(Field field) throws Exception { + public @Nullable Object defaultFor(Field field) { return null; } @@ -68,6 +82,21 @@ public EvaluationEnvironmentBuilder getBaseEnvironment() { * Called when parsing of the section is completed * and no more changes will be applied */ - public void afterParsing(List fields) throws Exception {} + public void afterParsing(List fields) throws Exception { + for (Field field : fields) { + if (field.get(this) != null) + continue; + + DefaultSupplier defaultSupplier = fieldDefaultSuppliers.get(field.getType()); + if (defaultSupplier == null || defaultSupplier.fieldExceptions.contains(field.getName())) + continue; + + field.set(this, defaultSupplier.supplier.get()); + } + } + + protected void registerFieldDefault(Class type, Supplier supplier, String... fieldExceptions) { + fieldDefaultSuppliers.put(type, new DefaultSupplier(supplier, Arrays.stream(fieldExceptions).collect(Collectors.toSet()))); + } } diff --git a/src/test/java/me/blvckbytes/bbconfigmapper/TestHelper.java b/src/test/java/me/blvckbytes/bbconfigmapper/TestHelper.java index bc06ffc..174ae96 100644 --- a/src/test/java/me/blvckbytes/bbconfigmapper/TestHelper.java +++ b/src/test/java/me/blvckbytes/bbconfigmapper/TestHelper.java @@ -116,8 +116,9 @@ public void assertSave(String fileName, YamlConfig config) throws Exception { StringWriter writer = new StringWriter(); assertDoesNotThrow(() -> config.save(writer)); List fileContents = Files.readAllLines(Paths.get("src/test/resources/save/" + fileName)); - List writerContents = Arrays.asList(writer.toString().split("\n")); - assertLinesMatch(fileContents, writerContents); + String writerString = writer.toString(); + List writerContents = Arrays.asList(writerString.split("\n")); + assertLinesMatch(fileContents, writerContents, "written: \n" + writerString); } /** diff --git a/src/test/java/me/blvckbytes/bbconfigmapper/YamlConfigExtensionTests.java b/src/test/java/me/blvckbytes/bbconfigmapper/YamlConfigExtensionTests.java index 6373777..510a58a 100644 --- a/src/test/java/me/blvckbytes/bbconfigmapper/YamlConfigExtensionTests.java +++ b/src/test/java/me/blvckbytes/bbconfigmapper/YamlConfigExtensionTests.java @@ -60,7 +60,7 @@ public void shouldExtendKeysWithScalarValues() throws Exception { @Test public void shouldNotExtendCommentedKeys() throws Exception { - char[] caseSuffixes = {'a', 'b', 'c'}; + char[] caseSuffixes = {'a', 'b', 'c', 'd'}; for (char caseSuffix : caseSuffixes) { YamlConfig baseConfig = helper.makeConfig("commented_keys_base_" + caseSuffix + ".yml"); diff --git a/src/test/resources/commented_keys_base_a.yml b/src/test/resources/commented_keys_base_a.yml index 059dfc3..911eccd 100644 --- a/src/test/resources/commented_keys_base_a.yml +++ b/src/test/resources/commented_keys_base_a.yml @@ -5,4 +5,9 @@ a: # e: 4 f: 5 g: - h: 2 \ No newline at end of file + h: 2 +i: + j: + k: 1 + l: 2 + m: 3 diff --git a/src/test/resources/commented_keys_base_b.yml b/src/test/resources/commented_keys_base_b.yml index 059dfc3..911eccd 100644 --- a/src/test/resources/commented_keys_base_b.yml +++ b/src/test/resources/commented_keys_base_b.yml @@ -5,4 +5,9 @@ a: # e: 4 f: 5 g: - h: 2 \ No newline at end of file + h: 2 +i: + j: + k: 1 + l: 2 + m: 3 diff --git a/src/test/resources/commented_keys_base_c.yml b/src/test/resources/commented_keys_base_c.yml index 08b6aef..fa96925 100644 --- a/src/test/resources/commented_keys_base_c.yml +++ b/src/test/resources/commented_keys_base_c.yml @@ -5,4 +5,9 @@ a: e: 4 f: 5 g: - # h: 2 \ No newline at end of file + # h: 2 +i: + j: + k: 1 + l: 2 + m: 3 diff --git a/src/test/resources/commented_keys_base_d.yml b/src/test/resources/commented_keys_base_d.yml new file mode 100644 index 0000000..e6c5c57 --- /dev/null +++ b/src/test/resources/commented_keys_base_d.yml @@ -0,0 +1,13 @@ +a: + b: 1 + c: 2 + d: 3 + e: 4 + f: 5 +g: + h: 2 +#i: +# j: +# k: 1 +# l: 2 +# m: 3 diff --git a/src/test/resources/commented_keys_extension.yml b/src/test/resources/commented_keys_extension.yml index 441a0bd..33c0c7e 100644 --- a/src/test/resources/commented_keys_extension.yml +++ b/src/test/resources/commented_keys_extension.yml @@ -6,3 +6,8 @@ a: f: 5 g: h: 2 +i: + j: + k: 1 + l: 2 + m: 3 \ No newline at end of file diff --git a/src/test/resources/save/commented_keys_save_a.yml b/src/test/resources/save/commented_keys_save_a.yml index 593659a..7e6b621 100644 --- a/src/test/resources/save/commented_keys_save_a.yml +++ b/src/test/resources/save/commented_keys_save_a.yml @@ -6,3 +6,8 @@ a: f: 5 g: h: 2 +i: + j: + k: 1 + l: 2 + m: 3 diff --git a/src/test/resources/save/commented_keys_save_b.yml b/src/test/resources/save/commented_keys_save_b.yml index 593659a..7e6b621 100644 --- a/src/test/resources/save/commented_keys_save_b.yml +++ b/src/test/resources/save/commented_keys_save_b.yml @@ -6,3 +6,8 @@ a: f: 5 g: h: 2 +i: + j: + k: 1 + l: 2 + m: 3 diff --git a/src/test/resources/save/commented_keys_save_c.yml b/src/test/resources/save/commented_keys_save_c.yml index 3dcff3d..6b8351b 100644 --- a/src/test/resources/save/commented_keys_save_c.yml +++ b/src/test/resources/save/commented_keys_save_c.yml @@ -5,4 +5,9 @@ a: e: 4 f: 5 g: -# h: 2 \ No newline at end of file +# h: 2 +i: + j: + k: 1 + l: 2 + m: 3 diff --git a/src/test/resources/save/commented_keys_save_d.yml b/src/test/resources/save/commented_keys_save_d.yml new file mode 100644 index 0000000..e6c5c57 --- /dev/null +++ b/src/test/resources/save/commented_keys_save_d.yml @@ -0,0 +1,13 @@ +a: + b: 1 + c: 2 + d: 3 + e: 4 + f: 5 +g: + h: 2 +#i: +# j: +# k: 1 +# l: 2 +# m: 3