From 1d45880311d26b49987313f183cfd1b5831efdc5 Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Tue, 11 Aug 2015 14:05:26 -0700 Subject: [PATCH 1/8] conditional base classes --- .../config/impl/ConfigConditional.java | 34 +++++++++++++ .../config/impl/ConfigDocumentParser.java | 51 +++++++++++++++++++ .../config/impl/ConfigNodeConditional.java | 41 +++++++++++++++ .../typesafe/config/impl/ConfigParser.java | 35 ++++++++++++- .../config/impl/SimpleConfigObject.java | 23 +++++++-- config/src/test/resources/conditional.conf | 14 +++++ .../typesafe/config/impl/ConfParserTest.scala | 12 +++++ .../impl/ConfigDocumentParserTest.scala | 1 + 8 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 config/src/main/java/com/typesafe/config/impl/ConfigConditional.java create mode 100644 config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java create mode 100644 config/src/test/resources/conditional.conf diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java b/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java new file mode 100644 index 00000000..b187a9c1 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java @@ -0,0 +1,34 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.ConfigException; + +import java.util.Map; + +public class ConfigConditional { + + private SubstitutionExpression left; + private AbstractConfigValue right; + private SimpleConfigObject body; + + ConfigConditional(SubstitutionExpression left, AbstractConfigValue right, SimpleConfigObject body) { + this.left = left; + this.right = right; + this.body = body; + + if (this.left.optional()) { + throw new ConfigException.BugOrBroken("Substitutions in conditional expressions cannot be optional"); + } + } + + public SimpleConfigObject resolve(Map values) { + AbstractConfigValue val = values.get(left.path().first()); + if (val == null) { + throw new ConfigException.BugOrBroken("Could not resolve substitution " + this.left.toString() + " in conditional expression"); + } + if (val.equals(this.right)) { + return this.body; + } else { + return null; + } + } +} diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java b/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java index 25ce3c4b..42735ce3 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java @@ -289,6 +289,11 @@ final class ConfigDocumentParser { && Tokens.getUnquotedText(t).equals("include"); } + private static boolean isIfKeyword(Token t) { + return Tokens.isUnquotedText(t) + && Tokens.getUnquotedText(t).equals("if"); + } + private static boolean isUnquotedWhitespace(Token t) { if (!Tokens.isUnquotedText(t)) return false; @@ -311,6 +316,47 @@ final class ConfigDocumentParser { } } + private ConfigNodeConditional parseConditional(ArrayList children) { + Token openToken = nextTokenCollectingWhitespace(children); + if (openToken != Tokens.OPEN_SQUARE) + throw parseError("expecting [ after if"); + children.add(new ConfigNodeSingleToken(openToken)); + + Token subToken = nextTokenCollectingWhitespace(children); + if (!Tokens.isSubstitution(subToken)) + throw parseError("left side of conditional expression must be a variable substitution"); + children.add(new ConfigNodeSingleToken(subToken)); + + Token eqToken = nextTokenCollectingWhitespace(children); + Token eq2Token = nextTokenCollectingWhitespace(children); + if (eqToken != Tokens.EQUALS || eq2Token != Tokens.EQUALS) + throw parseError("conditional check must be `==`"); + children.add(new ConfigNodeSingleToken(eqToken)); + children.add(new ConfigNodeSingleToken(eq2Token)); + + Token valToken = nextTokenCollectingWhitespace(children); + if (!Tokens.isValueWithType(valToken, ConfigValueType.STRING) + && !Tokens.isUnquotedText(valToken) + && !Tokens.isValueWithType(valToken, ConfigValueType.BOOLEAN)) + throw parseError("right side of conditional expression must be a string or boolean"); + children.add(new ConfigNodeSimpleValue(valToken)); + + Token closeToken = nextTokenCollectingWhitespace(children); + if (closeToken != Tokens.CLOSE_SQUARE) + throw parseError("expecting ] after conditional expression"); + children.add(new ConfigNodeSingleToken(closeToken)); + + Token openCurlyToken = nextTokenCollectingWhitespace(children); + if (openCurlyToken != Tokens.OPEN_CURLY) + throw parseError("must open a conditional body using {"); + + ArrayList importantChildren = new ArrayList(children); + + ConfigNodeComplexValue body = parseObject(true); + + return new ConfigNodeConditional(importantChildren, body); + } + private ConfigNodeInclude parseInclude(ArrayList children) { Token t = nextTokenCollectingWhitespace(children); @@ -391,6 +437,11 @@ final class ConfigDocumentParser { includeNodes.add(new ConfigNodeSingleToken(t)); objectNodes.add(parseInclude(includeNodes)); afterComma = false; + } else if (flavor != ConfigSyntax.JSON && isIfKeyword(t)) { + ArrayList ifNodes = new ArrayList(); + ifNodes.add(new ConfigNodeSingleToken(t)); + objectNodes.add(parseConditional(ifNodes)); + } else { keyValueNodes = new ArrayList(); Token keyToken = t; diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java new file mode 100644 index 00000000..c027caeb --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java @@ -0,0 +1,41 @@ +package com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +final class ConfigNodeConditional extends AbstractConfigNode { + final private ArrayList children; + final private AbstractConfigNodeValue body; + + ConfigNodeConditional(Collection children, AbstractConfigNodeValue body) { + this.children = new ArrayList(children); + this.body = body; + + } + + public AbstractConfigNodeValue body() { return body; } + public List children() { return children; } + + @Override + protected Collection tokens() { + ArrayList tokens = new ArrayList(); + for (AbstractConfigNode child : children) { + tokens.addAll(child.tokens()); + } + for (Token token : body.tokens()) { + tokens.add(token); + } + return tokens; + } +// +// protected String name() { +// for (AbstractConfigNode n : children) { +// if (n instanceof ConfigNodeSimpleValue) { +// return (String)Tokens.getValue(((ConfigNodeSimpleValue) n).token()).unwrapped(); +// } +// } +// return null; +// } +} + diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java index 159b8659..01791c4c 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java @@ -94,7 +94,7 @@ final class ConfigParser { } else if (n instanceof ConfigNodeObject) { v = parseObject((ConfigNodeObject)n); } else if (n instanceof ConfigNodeArray) { - v = parseArray((ConfigNodeArray)n); + v = parseArray((ConfigNodeArray) n); } else if (n instanceof ConfigNodeConcatenation) { v = parseConcatenation((ConfigNodeConcatenation)n); } else { @@ -192,10 +192,39 @@ final class ConfigParser { } } + private ConfigConditional parseConditional(ConfigNodeConditional n) { + SubstitutionExpression left = null; + AbstractConfigValue right = null; + + AbstractConfigObject body = parseObject((ConfigNodeObject) n.body()); + + for(AbstractConfigNode child: n.children()) { + if (child instanceof ConfigNodeSingleToken) { + Token token = ((ConfigNodeSingleToken) child).token(); + + if (Tokens.isSubstitution(token)) { + List pathTokens = Tokens.getSubstitutionPathExpression(token); + Path path = PathParser.parsePathExpression(pathTokens.iterator(), token.origin()); + left = new SubstitutionExpression(path, false); + } + } else if (child instanceof AbstractConfigNodeValue) { + right = parseValue((AbstractConfigNodeValue)child, null); + } + } + + if (left == null) + throw new ConfigException.BugOrBroken("Conditional expression did not have a left hand substitution"); + if (right == null) + throw new ConfigException.BugOrBroken("Conditional expression did not have a right hand value"); + + return new ConfigConditional(left, right, (SimpleConfigObject) body); + } + private AbstractConfigObject parseObject(ConfigNodeObject n) { Map values = new HashMap(); SimpleConfigOrigin objectOrigin = lineOrigin(); boolean lastWasNewline = false; + List conditionals = new ArrayList(); ArrayList nodes = new ArrayList(n.children()); List comments = new ArrayList(); @@ -212,8 +241,10 @@ final class ConfigParser { } lastWasNewline = true; } else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeInclude) { - parseInclude(values, (ConfigNodeInclude)node); + parseInclude(values, (ConfigNodeInclude) node); lastWasNewline = false; + } else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeConditional) { + conditionals.add(parseConditional((ConfigNodeConditional) node)); } else if (node instanceof ConfigNodeField) { lastWasNewline = false; Path path = ((ConfigNodeField) node).path().value(); diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index cf5f5ea0..9c92463c 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -28,12 +28,15 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa // this map should never be modified - assume immutable final private Map value; + final private List conditionals; final private boolean resolved; final private boolean ignoresFallbacks; SimpleConfigObject(ConfigOrigin origin, - Map value, ResolveStatus status, - boolean ignoresFallbacks) { + Map value, + ResolveStatus status, + boolean ignoresFallbacks, + List conditionals) { super(origin); if (value == null) throw new ConfigException.BugOrBroken( @@ -41,15 +44,29 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa this.value = value; this.resolved = status == ResolveStatus.RESOLVED; this.ignoresFallbacks = ignoresFallbacks; + this.conditionals = conditionals; // Kind of an expensive debug check. Comment out? if (status != ResolveStatus.fromValues(value.values())) throw new ConfigException.BugOrBroken("Wrong resolved status on " + this); } + SimpleConfigObject(ConfigOrigin origin, + Map value, + ResolveStatus resolveStatus, + boolean ignoresFallbacks) { + this(origin, value, resolveStatus, ignoresFallbacks, new ArrayList()); + } + SimpleConfigObject(ConfigOrigin origin, Map value) { - this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */); + this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, new ArrayList()); + } + + SimpleConfigObject(ConfigOrigin origin, + Map value, + List conditionals) { + this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, conditionals); } @Override diff --git a/config/src/test/resources/conditional.conf b/config/src/test/resources/conditional.conf new file mode 100644 index 00000000..90c96cd9 --- /dev/null +++ b/config/src/test/resources/conditional.conf @@ -0,0 +1,14 @@ +shouldDoIt: true +a: { + if [${shouldDoIt} == true] { + b: "b" + c: { + d: "d" + } + } + x: { + if [${shouldDoIt} == false] { + e: "e" + } + } +} \ No newline at end of file diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index 1a7548df..194968fd 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -809,6 +809,18 @@ class ConfParserTest extends TestUtils { assertEquals(resolved.getConfigList("a").get(0).getString("replace-me"), "replaced") } + @Test + def conditionals() { + val conf = ConfigFactory.parseResources("conditional.conf") + val resolved = conf.resolve() + + assertEquals(resolved.getConfig("a").getString("b"), "b") + assertEquals(resolved.getConfig("a").getConfig("c").getString("d"), "d") + intercept[Exception] { + resolved.getConfig("a").getString("e") + } + } + @Test def acceptBOMStartingFile() { // BOM at start of file should be ignored diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala index fb8bca20..20460754 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala @@ -62,6 +62,7 @@ class ConfigDocumentParserTest extends TestUtils { parseTest("foo:bar") parseTest(" foo : bar ") parseTest("""include "foo.conf" """) + parseTest("if [${foo} == bar] { key: value }"); parseTest(" \nfoo:bar\n ") // Can parse a map with all simple types From 48ecd996e8e45dd8144079df4b08aed85894d189 Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Tue, 11 Aug 2015 14:36:10 -0700 Subject: [PATCH 2/8] conditional base classes --- .../config/impl/ConfigConditional.java | 20 +++++++------- .../typesafe/config/impl/ConfigParser.java | 2 +- .../typesafe/config/impl/ResolveStatus.java | 4 ++- .../config/impl/SimpleConfigList.java | 5 ++-- .../config/impl/SimpleConfigObject.java | 27 ++++++++++++------- 5 files changed, 35 insertions(+), 23 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java b/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java index b187a9c1..d53b1bd9 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java @@ -20,15 +20,17 @@ public class ConfigConditional { } } - public SimpleConfigObject resolve(Map values) { - AbstractConfigValue val = values.get(left.path().first()); - if (val == null) { - throw new ConfigException.BugOrBroken("Could not resolve substitution " + this.left.toString() + " in conditional expression"); - } - if (val.equals(this.right)) { - return this.body; - } else { - return null; + public SimpleConfigObject resolve(ResolveContext context, ResolveSource source) { + try { + AbstractConfigValue val = source.lookupSubst(context, this.left, 0).result.value; + + if (val.equals(this.right)) { + return this.body; + } else { + return SimpleConfigObject.empty(); + } + } catch (AbstractConfigValue.NotPossibleToResolve e){ + throw new ConfigException.BugOrBroken("Could not resolve left side of conditional expression: " + this.left.toString()); } } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java index 01791c4c..d63bc5ee 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java @@ -338,7 +338,7 @@ final class ConfigParser { } } - return new SimpleConfigObject(objectOrigin, values); + return new SimpleConfigObject(objectOrigin, values, conditionals); } private SimpleConfigList parseArray(ConfigNodeArray n) { diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveStatus.java b/config/src/main/java/com/typesafe/config/impl/ResolveStatus.java index 8deeaf52..96fd5625 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveStatus.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveStatus.java @@ -12,11 +12,13 @@ enum ResolveStatus { UNRESOLVED, RESOLVED; final static ResolveStatus fromValues( - Collection values) { + Collection values, Collection conditionals) { for (AbstractConfigValue v : values) { if (v.resolveStatus() == ResolveStatus.UNRESOLVED) return ResolveStatus.UNRESOLVED; } + if (conditionals.size() > 0) + return ResolveStatus.UNRESOLVED; return ResolveStatus.RESOLVED; } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java index a8d8e366..31f088f6 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -26,8 +26,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList, final private boolean resolved; SimpleConfigList(ConfigOrigin origin, List value) { - this(origin, value, ResolveStatus - .fromValues(value)); + this(origin, value, ResolveStatus.fromValues(value, new ArrayList())); } SimpleConfigList(ConfigOrigin origin, List value, @@ -37,7 +36,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList, this.resolved = status == ResolveStatus.RESOLVED; // kind of an expensive debug check (makes this constructor pointless) - if (status != ResolveStatus.fromValues(value)) + if (status != ResolveStatus.fromValues(value, new ArrayList())) throw new ConfigException.BugOrBroken( "SimpleConfigList created with wrong resolve status: " + this); } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 9c92463c..01717cec 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -47,7 +47,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa this.conditionals = conditionals; // Kind of an expensive debug check. Comment out? - if (status != ResolveStatus.fromValues(value.values())) + if (status != ResolveStatus.fromValues(value.values(), conditionals)) throw new ConfigException.BugOrBroken("Wrong resolved status on " + this); } @@ -60,13 +60,13 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa SimpleConfigObject(ConfigOrigin origin, Map value) { - this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, new ArrayList()); + this(origin, value, ResolveStatus.fromValues(value.values(), new ArrayList()), false /* ignoresFallbacks */, new ArrayList()); } SimpleConfigObject(ConfigOrigin origin, Map value, List conditionals) { - this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, conditionals); + this(origin, value, ResolveStatus.fromValues(value.values(), conditionals), false /* ignoresFallbacks */, conditionals); } @Override @@ -131,8 +131,8 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa Map updated = new HashMap( value); updated.put(key, v); - return new SimpleConfigObject(origin(), updated, ResolveStatus.fromValues(updated - .values()), ignoresFallbacks); + return new SimpleConfigObject(origin(), updated, + ResolveStatus.fromValues(updated.values(), conditionals), ignoresFallbacks); } else if (next != null || v == null) { // can't descend, nothing to remove return this; @@ -143,8 +143,8 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa if (!old.getKey().equals(key)) smaller.put(old.getKey(), old.getValue()); } - return new SimpleConfigObject(origin(), smaller, ResolveStatus.fromValues(smaller - .values()), ignoresFallbacks); + return new SimpleConfigObject(origin(), smaller, + ResolveStatus.fromValues(smaller.values(), conditionals), ignoresFallbacks); } } @@ -162,7 +162,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa newMap.put(key, (AbstractConfigValue) v); } - return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values()), + return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values(), conditionals), ignoresFallbacks); } @@ -225,7 +225,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa else newChildren.remove(old.getKey()); - return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values()), + return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values(), conditionals), ignoresFallbacks); } } @@ -322,6 +322,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa private SimpleConfigObject modifyMayThrow(Modifier modifier) throws Exception { Map changes = null; + for (String k : keySet()) { AbstractConfigValue v = value.get(k); // "modified" may be null, which means remove the child; @@ -413,6 +414,14 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa ResolveModifier modifier = new ResolveModifier(context, sourceWithParent); AbstractConfigValue value = modifyMayThrow(modifier); + + for (ConfigConditional cond: this.conditionals) { + SimpleConfigObject body = cond.resolve(context, sourceWithParent); + AbstractConfigObject resolvedBody = body.resolveSubstitutions(context, source).value; + value = this.mergedWithObject(resolvedBody); + } + this.conditionals.clear(); + return ResolveResult.make(modifier.context, value).asObjectResult(); } catch (NotPossibleToResolve e) { throw e; From 091ae19e166e9c6ede348bfad3558ea09a51cc79 Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Thu, 13 Aug 2015 14:42:46 -0700 Subject: [PATCH 3/8] conditionals --- .../config/impl/SimpleConfigObject.java | 18 +++++++++--------- config/src/test/resources/conditional.conf | 16 ++++++++++------ .../typesafe/config/impl/ConfParserTest.scala | 5 +++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 01717cec..20255d3c 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -28,7 +28,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa // this map should never be modified - assume immutable final private Map value; - final private List conditionals; + final private Collection conditionals; final private boolean resolved; final private boolean ignoresFallbacks; @@ -36,7 +36,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa Map value, ResolveStatus status, boolean ignoresFallbacks, - List conditionals) { + Collection conditionals) { super(origin); if (value == null) throw new ConfigException.BugOrBroken( @@ -193,13 +193,13 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa } private SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin, - boolean newIgnoresFallbacks) { - return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks); + boolean newIgnoresFallbacks, Collection conditionals) { + return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks, conditionals); } @Override protected SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin) { - return newCopy(newStatus, newOrigin, ignoresFallbacks); + return newCopy(newStatus, newOrigin, ignoresFallbacks, conditionals); } @Override @@ -207,7 +207,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa if (ignoresFallbacks) return this; else - return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */); + return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */, conditionals); } @Override @@ -305,7 +305,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus, newIgnoresFallbacks); else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks()) - return newCopy(newResolveStatus, origin(), newIgnoresFallbacks); + return newCopy(newResolveStatus, origin(), newIgnoresFallbacks, new ArrayList()); else return this; } @@ -418,9 +418,9 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa for (ConfigConditional cond: this.conditionals) { SimpleConfigObject body = cond.resolve(context, sourceWithParent); AbstractConfigObject resolvedBody = body.resolveSubstitutions(context, source).value; - value = this.mergedWithObject(resolvedBody); +// if (resolvedBody != SimpleConfigObject.empty()) + value = value.mergedWithObject(resolvedBody); } - this.conditionals.clear(); return ResolveResult.make(modifier.context, value).asObjectResult(); } catch (NotPossibleToResolve e) { diff --git a/config/src/test/resources/conditional.conf b/config/src/test/resources/conditional.conf index 90c96cd9..9da3b967 100644 --- a/config/src/test/resources/conditional.conf +++ b/config/src/test/resources/conditional.conf @@ -2,13 +2,17 @@ shouldDoIt: true a: { if [${shouldDoIt} == true] { b: "b" - c: { - d: "d" + } + if [${shouldDoIt} == true] { + c: "c" + nested: { + n: "n" + if [${a.c} == "c"] { + works: true + } } } - x: { - if [${shouldDoIt} == false] { - e: "e" - } + if [${shouldDoIt} == false] { + d: "d" } } \ No newline at end of file diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index 194968fd..bb6d67f3 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -815,9 +815,10 @@ class ConfParserTest extends TestUtils { val resolved = conf.resolve() assertEquals(resolved.getConfig("a").getString("b"), "b") - assertEquals(resolved.getConfig("a").getConfig("c").getString("d"), "d") + assertEquals(resolved.getConfig("a").getString("c"), "c") + assertEquals(resolved.getConfig("a").getConfig("nested").getBoolean("works"), true) intercept[Exception] { - resolved.getConfig("a").getString("e") + resolved.getConfig("a").getConfig("d") } } From 1ab4def03d05534500af5f8ae7e6b2e0cd1ade9b Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Thu, 13 Aug 2015 14:46:03 -0700 Subject: [PATCH 4/8] test case --- config/src/test/resources/conditional.conf | 1 + .../test/scala/com/typesafe/config/impl/ConfParserTest.scala | 2 ++ 2 files changed, 3 insertions(+) diff --git a/config/src/test/resources/conditional.conf b/config/src/test/resources/conditional.conf index 9da3b967..a7cd38b4 100644 --- a/config/src/test/resources/conditional.conf +++ b/config/src/test/resources/conditional.conf @@ -5,6 +5,7 @@ a: { } if [${shouldDoIt} == true] { c: "c" + f: ${a.b} nested: { n: "n" if [${a.c} == "c"] { diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index bb6d67f3..958da0e7 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -816,6 +816,8 @@ class ConfParserTest extends TestUtils { assertEquals(resolved.getConfig("a").getString("b"), "b") assertEquals(resolved.getConfig("a").getString("c"), "c") + assertEquals(resolved.getConfig("a").getString("f"), "b") + assertEquals(resolved.getConfig("a").getConfig("nested").getBoolean("works"), true) intercept[Exception] { resolved.getConfig("a").getConfig("d") From 2566973fdd07fd7f1cdee96a496b9746cd8cef16 Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Thu, 13 Aug 2015 15:02:40 -0700 Subject: [PATCH 5/8] formatting --- .../main/java/com/typesafe/config/impl/SimpleConfigObject.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 20255d3c..7f437f37 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -418,8 +418,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa for (ConfigConditional cond: this.conditionals) { SimpleConfigObject body = cond.resolve(context, sourceWithParent); AbstractConfigObject resolvedBody = body.resolveSubstitutions(context, source).value; -// if (resolvedBody != SimpleConfigObject.empty()) - value = value.mergedWithObject(resolvedBody); + value = value.mergedWithObject(resolvedBody); } return ResolveResult.make(modifier.context, value).asObjectResult(); From 6fede7cf7353752428e19a4b403972a8d4e9271c Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Fri, 14 Aug 2015 12:24:12 -0700 Subject: [PATCH 6/8] per review --- .../java/com/typesafe/config/impl/ConfigConditional.java | 2 +- .../com/typesafe/config/impl/ConfigNodeConditional.java | 9 --------- .../main/java/com/typesafe/config/impl/ConfigParser.java | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java b/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java index d53b1bd9..56a0a390 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigConditional.java @@ -16,7 +16,7 @@ public class ConfigConditional { this.body = body; if (this.left.optional()) { - throw new ConfigException.BugOrBroken("Substitutions in conditional expressions cannot be optional"); + throw new ConfigException.BugOrBroken("Substitution " + this.left.toString() + " in conditional expression cannot be optional"); } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java index c027caeb..ad95bb4a 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeConditional.java @@ -28,14 +28,5 @@ final class ConfigNodeConditional extends AbstractConfigNode { } return tokens; } -// -// protected String name() { -// for (AbstractConfigNode n : children) { -// if (n instanceof ConfigNodeSimpleValue) { -// return (String)Tokens.getValue(((ConfigNodeSimpleValue) n).token()).unwrapped(); -// } -// } -// return null; -// } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java index d63bc5ee..9beafc06 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java @@ -94,7 +94,7 @@ final class ConfigParser { } else if (n instanceof ConfigNodeObject) { v = parseObject((ConfigNodeObject)n); } else if (n instanceof ConfigNodeArray) { - v = parseArray((ConfigNodeArray) n); + v = parseArray((ConfigNodeArray)n); } else if (n instanceof ConfigNodeConcatenation) { v = parseConcatenation((ConfigNodeConcatenation)n); } else { From e5910b8739919da465ee57445c9ec86b122809b0 Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Tue, 18 Aug 2015 09:50:37 -0700 Subject: [PATCH 7/8] documentation --- HOCON.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/HOCON.md b/HOCON.md index 418310c8..30f8e457 100644 --- a/HOCON.md +++ b/HOCON.md @@ -22,6 +22,7 @@ - [Note: Arrays without commas or newlines](#note-arrays-without-commas-or-newlines) - [Path expressions](#path-expressions) - [Paths as keys](#paths-as-keys) + - [Conditional expressions](#conditional-expressions) - [Substitutions](#substitutions) - [Self-Referential Substitutions](#self-referential-substitutions) - [The `+=` field separator](#the--field-separator) @@ -719,6 +720,23 @@ resolving an optional substitution (i.e. the `${?foo}` syntax). If `${?foo}` refers to itself then it's as if it referred to a nonexistent value. +### Conditional expressions + +Conditional expressions allow for a block of configuration to be +included or omitted based on the value of a substitution. + +Example conditional expression: + + - `if [${a} == "a"] { b: true }` + +In this case, if the substitution ${a} was equal to the string "a" +then the object { b: true } would be merged into the object that +the conditional expression was declared inside. If it was not +equal to "a" nothing would be merged. + +The left hand side substitution cannot be optional. Currently, only equality comparisons are supported. The right hand side +of the expression can be any string, quoted or unquoted, or a boolean. + #### The `+=` field separator Fields may have `+=` as a separator rather than `:` or `=`. A From 94dd4c97bde092a9143a531adf7f5b3302efb4c9 Mon Sep 17 00:00:00 2001 From: Ryan O'Neill Date: Tue, 18 Aug 2015 09:56:17 -0700 Subject: [PATCH 8/8] doc ToC ordering --- HOCON.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/HOCON.md b/HOCON.md index 30f8e457..dde47648 100644 --- a/HOCON.md +++ b/HOCON.md @@ -22,11 +22,11 @@ - [Note: Arrays without commas or newlines](#note-arrays-without-commas-or-newlines) - [Path expressions](#path-expressions) - [Paths as keys](#paths-as-keys) - - [Conditional expressions](#conditional-expressions) - [Substitutions](#substitutions) - [Self-Referential Substitutions](#self-referential-substitutions) - [The `+=` field separator](#the--field-separator) - [Examples of Self-Referential Substitutions](#examples-of-self-referential-substitutions) + - [Conditional expressions](#conditional-expressions) - [Includes](#includes) - [Include syntax](#include-syntax) - [Include semantics: merging](#include-semantics-merging) @@ -720,23 +720,6 @@ resolving an optional substitution (i.e. the `${?foo}` syntax). If `${?foo}` refers to itself then it's as if it referred to a nonexistent value. -### Conditional expressions - -Conditional expressions allow for a block of configuration to be -included or omitted based on the value of a substitution. - -Example conditional expression: - - - `if [${a} == "a"] { b: true }` - -In this case, if the substitution ${a} was equal to the string "a" -then the object { b: true } would be merged into the object that -the conditional expression was declared inside. If it was not -equal to "a" nothing would be merged. - -The left hand side substitution cannot be optional. Currently, only equality comparisons are supported. The right hand side -of the expression can be any string, quoted or unquoted, or a boolean. - #### The `+=` field separator Fields may have `+=` as a separator rather than `:` or `=`. A @@ -913,6 +896,23 @@ rather than by the path inside the `${}` expression, because substitutions may be resolved differently depending on their position in the file. +### Conditional expressions + +Conditional expressions allow for a block of configuration to be +included or omitted based on the value of a substitution. + +Example conditional expression: + + - `if [${a} == "a"] { b: true }` + +In this case, if the substitution ${a} was equal to the string "a" +then the object { b: true } would be merged into the object that +the conditional expression was declared inside. If it was not +equal to "a" nothing would be merged. + +The left hand side substitution cannot be optional. Currently, only equality comparisons are supported. The right hand side +of the expression can be any string, quoted or unquoted, or a boolean. + ### Includes #### Include syntax