conditional base classes

This commit is contained in:
Ryan O'Neill 2015-08-11 14:05:26 -07:00
parent 0755daa9db
commit 1d45880311
8 changed files with 206 additions and 5 deletions

View File

@ -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<String, AbstractConfigValue> 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;
}
}
}

View File

@ -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<AbstractConfigNode> 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<AbstractConfigNode> importantChildren = new ArrayList<AbstractConfigNode>(children);
ConfigNodeComplexValue body = parseObject(true);
return new ConfigNodeConditional(importantChildren, body);
}
private ConfigNodeInclude parseInclude(ArrayList<AbstractConfigNode> 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<AbstractConfigNode> ifNodes = new ArrayList<AbstractConfigNode>();
ifNodes.add(new ConfigNodeSingleToken(t));
objectNodes.add(parseConditional(ifNodes));
} else {
keyValueNodes = new ArrayList<AbstractConfigNode>();
Token keyToken = t;

View File

@ -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<AbstractConfigNode> children;
final private AbstractConfigNodeValue body;
ConfigNodeConditional(Collection<AbstractConfigNode> children, AbstractConfigNodeValue body) {
this.children = new ArrayList<AbstractConfigNode>(children);
this.body = body;
}
public AbstractConfigNodeValue body() { return body; }
public List<AbstractConfigNode> children() { return children; }
@Override
protected Collection<Token> tokens() {
ArrayList<Token> tokens = new ArrayList<Token>();
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;
// }
}

View File

@ -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<Token> 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<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
SimpleConfigOrigin objectOrigin = lineOrigin();
boolean lastWasNewline = false;
List<ConfigConditional> conditionals = new ArrayList<ConfigConditional>();
ArrayList<AbstractConfigNode> nodes = new ArrayList<AbstractConfigNode>(n.children());
List<String> comments = new ArrayList<String>();
@ -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();

View File

@ -28,12 +28,15 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
// this map should never be modified - assume immutable
final private Map<String, AbstractConfigValue> value;
final private List<ConfigConditional> conditionals;
final private boolean resolved;
final private boolean ignoresFallbacks;
SimpleConfigObject(ConfigOrigin origin,
Map<String, AbstractConfigValue> value, ResolveStatus status,
boolean ignoresFallbacks) {
Map<String, AbstractConfigValue> value,
ResolveStatus status,
boolean ignoresFallbacks,
List<ConfigConditional> 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<String, AbstractConfigValue> value,
ResolveStatus resolveStatus,
boolean ignoresFallbacks) {
this(origin, value, resolveStatus, ignoresFallbacks, new ArrayList<ConfigConditional>());
}
SimpleConfigObject(ConfigOrigin origin,
Map<String, AbstractConfigValue> value) {
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */);
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, new ArrayList<ConfigConditional>());
}
SimpleConfigObject(ConfigOrigin origin,
Map<String, AbstractConfigValue> value,
List<ConfigConditional> conditionals) {
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, conditionals);
}
@Override

View File

@ -0,0 +1,14 @@
shouldDoIt: true
a: {
if [${shouldDoIt} == true] {
b: "b"
c: {
d: "d"
}
}
x: {
if [${shouldDoIt} == false] {
e: "e"
}
}
}

View File

@ -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

View File

@ -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