mirror of
https://github.com/lightbend/config.git
synced 2025-03-22 23:30:27 +08:00
Address PR feedback
Address numerous pieces of PR feedback, including: * Change ConfigDocumentParser's parse method to take a ConfigOrigin as a parameter, and uses this origin when throwing Parse exceptions rather than a dummy origin * Pass in the type of an include node on construction so it does not have to parse the information out itself * Add tests to ensure an empty object is created inside a ConfigNodeRoot when an empty document is given * Remove unused instance variables or variables duplicated between the two parsers
This commit is contained in:
parent
13cb4785b9
commit
74070b44cc
@ -21,7 +21,8 @@ public interface ConfigDocument {
|
||||
* Returns a new ConfigDocument that is a copy of the current ConfigDocument,
|
||||
* but with the desired value set at the desired path. If the path exists, it will
|
||||
* remove all duplicates before the final occurrence of the path, and replace the value
|
||||
* at the final occurrence of the path. If the path does not exist, it will be added.
|
||||
* at the final occurrence of the path. If the path does not exist, it will be added. If
|
||||
* the document has an array as the root value, an exception will be thrown.
|
||||
*
|
||||
* @param path the path at which to set the desired value
|
||||
* @param newValue the value to set at the desired path, represented as a string. This
|
||||
|
@ -5,24 +5,17 @@ package com.typesafe.config.impl;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigParseOptions;
|
||||
import com.typesafe.config.ConfigSyntax;
|
||||
import com.typesafe.config.ConfigValueType;
|
||||
import com.typesafe.config.*;
|
||||
|
||||
final class ConfigDocumentParser {
|
||||
static ConfigNodeRoot parse(Iterator<Token> tokens, ConfigParseOptions options) {
|
||||
ParseContext context = new ParseContext(options.getSyntax(), tokens);
|
||||
static ConfigNodeRoot parse(Iterator<Token> tokens, ConfigOrigin origin, ConfigParseOptions options) {
|
||||
ConfigSyntax syntax = options.getSyntax() == null ? ConfigSyntax.CONF : options.getSyntax();
|
||||
ParseContext context = new ParseContext(syntax, origin, tokens);
|
||||
return context.parse();
|
||||
}
|
||||
|
||||
static ConfigNodeComplexValue parse(Iterator<Token> tokens) {
|
||||
ParseContext context = new ParseContext(ConfigSyntax.CONF, tokens);
|
||||
return context.parse();
|
||||
}
|
||||
|
||||
static AbstractConfigNodeValue parseValue(Iterator<Token> tokens, ConfigParseOptions options) {
|
||||
ParseContext context = new ParseContext(options.getSyntax(), tokens);
|
||||
static AbstractConfigNodeValue parseValue(Iterator<Token> tokens, ConfigOrigin origin, ConfigParseOptions options) {
|
||||
ParseContext context = new ParseContext(options.getSyntax(), origin, tokens);
|
||||
return context.parseSingleValue();
|
||||
}
|
||||
|
||||
@ -31,24 +24,19 @@ final class ConfigDocumentParser {
|
||||
final private Stack<Token> buffer;
|
||||
final private Iterator<Token> tokens;
|
||||
final private ConfigSyntax flavor;
|
||||
final private LinkedList<Path> pathStack;
|
||||
final private ConfigOrigin baseOrigin;
|
||||
// this is the number of "equals" we are inside,
|
||||
// used to modify the error message to reflect that
|
||||
// someone may think this is .properties format.
|
||||
int equalsCount;
|
||||
// the number of lists we are inside; this is used to detect the "cannot
|
||||
// generate a reference to a list element" problem, and once we fix that
|
||||
// problem we should be able to get rid of this variable.
|
||||
int arrayCount;
|
||||
|
||||
ParseContext(ConfigSyntax flavor, Iterator<Token> tokens) {
|
||||
ParseContext(ConfigSyntax flavor, ConfigOrigin origin, Iterator<Token> tokens) {
|
||||
lineNumber = 1;
|
||||
buffer = new Stack<Token>();
|
||||
this.tokens = tokens;
|
||||
this.flavor = flavor;
|
||||
this.pathStack = new LinkedList<Path>();
|
||||
this.equalsCount = 0;
|
||||
this.arrayCount = 0;
|
||||
this.baseOrigin = origin;
|
||||
}
|
||||
|
||||
private Token popToken() {
|
||||
@ -62,16 +50,16 @@ final class ConfigDocumentParser {
|
||||
Token t = popToken();
|
||||
if (flavor == ConfigSyntax.JSON) {
|
||||
if (Tokens.isUnquotedText(t) && !isUnquotedWhitespace(t)) {
|
||||
throw parseError(addKeyName("Token not allowed in valid JSON: '"
|
||||
+ Tokens.getUnquotedText(t) + "'"));
|
||||
throw parseError("Token not allowed in valid JSON: '"
|
||||
+ Tokens.getUnquotedText(t) + "'");
|
||||
} else if (Tokens.isSubstitution(t)) {
|
||||
throw parseError(addKeyName("Substitutions (${} syntax) not allowed in JSON"));
|
||||
throw parseError("Substitutions (${} syntax) not allowed in JSON");
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
private Token nextTokenIgnoringWhitespace(Collection<AbstractConfigNode> nodes) {
|
||||
private Token nextTokenCollectingWhitespace(Collection<AbstractConfigNode> nodes) {
|
||||
while (true) {
|
||||
Token t = nextToken();
|
||||
if (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t)) {
|
||||
@ -102,7 +90,7 @@ final class ConfigDocumentParser {
|
||||
// is left just after the comma or the newline.
|
||||
private boolean checkElementSeparator(Collection<AbstractConfigNode> nodes) {
|
||||
if (flavor == ConfigSyntax.JSON) {
|
||||
Token t = nextTokenIgnoringWhitespace(nodes);
|
||||
Token t = nextTokenCollectingWhitespace(nodes);
|
||||
if (t == Tokens.COMMA) {
|
||||
nodes.add(new ConfigNodeSingleToken(t));
|
||||
return true;
|
||||
@ -115,14 +103,13 @@ final class ConfigDocumentParser {
|
||||
Token t = nextToken();
|
||||
while (true) {
|
||||
if (Tokens.isIgnoredWhitespace(t) || isUnquotedWhitespace(t)) {
|
||||
//do nothing
|
||||
nodes.add(new ConfigNodeSingleToken(t));
|
||||
} else if (Tokens.isComment(t)) {
|
||||
nodes.add(new ConfigNodeComment(t));
|
||||
t = nextToken();
|
||||
continue;
|
||||
} else if (Tokens.isNewline(t)) {
|
||||
sawSeparatorOrNewline = true;
|
||||
lineNumber++;
|
||||
nodes.add(new ConfigNodeSingleToken(t));
|
||||
// we want to continue to also eat
|
||||
// a comma if there is one.
|
||||
} else if (t == Tokens.COMMA) {
|
||||
@ -133,7 +120,6 @@ final class ConfigDocumentParser {
|
||||
putBack(t);
|
||||
return sawSeparatorOrNewline;
|
||||
}
|
||||
nodes.add(new ConfigNodeSingleToken(t));
|
||||
t = nextToken();
|
||||
}
|
||||
}
|
||||
@ -150,7 +136,7 @@ final class ConfigDocumentParser {
|
||||
int valueCount = 0;
|
||||
|
||||
// ignore a newline up front
|
||||
Token t = nextTokenIgnoringWhitespace(nodes);
|
||||
Token t = nextTokenCollectingWhitespace(nodes);
|
||||
while (true) {
|
||||
AbstractConfigNodeValue v = null;
|
||||
if (Tokens.isIgnoredWhitespace(t)) {
|
||||
@ -211,29 +197,7 @@ final class ConfigDocumentParser {
|
||||
}
|
||||
|
||||
private ConfigException parseError(String message, Throwable cause) {
|
||||
return new ConfigException.Parse(SimpleConfigOrigin.newSimple("").withLineNumber(lineNumber), message, cause);
|
||||
}
|
||||
|
||||
private String previousFieldName(Path lastPath) {
|
||||
if (lastPath != null) {
|
||||
return lastPath.render();
|
||||
} else if (pathStack.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return pathStack.peek().render();
|
||||
}
|
||||
|
||||
private String previousFieldName() {
|
||||
return previousFieldName(null);
|
||||
}
|
||||
|
||||
private String addKeyName(String message) {
|
||||
String previousFieldName = previousFieldName();
|
||||
if (previousFieldName != null) {
|
||||
return "in value for key '" + previousFieldName + "': " + message;
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
return new ConfigException.Parse(baseOrigin.withLineNumber(lineNumber), message, cause);
|
||||
}
|
||||
|
||||
private String addQuoteSuggestion(String badToken, String message) {
|
||||
@ -242,7 +206,7 @@ final class ConfigDocumentParser {
|
||||
|
||||
private String addQuoteSuggestion(Path lastPath, boolean insideEquals, String badToken,
|
||||
String message) {
|
||||
String previousFieldName = previousFieldName(lastPath);
|
||||
String previousFieldName = lastPath != null ? lastPath.render() : null;
|
||||
|
||||
String part;
|
||||
if (badToken.equals(Tokens.END.toString())) {
|
||||
@ -274,8 +238,6 @@ final class ConfigDocumentParser {
|
||||
|
||||
private AbstractConfigNodeValue parseValue(Token t) {
|
||||
AbstractConfigNodeValue v = null;
|
||||
|
||||
int startingArrayCount = arrayCount;
|
||||
int startingEqualsCount = equalsCount;
|
||||
|
||||
if (Tokens.isValue(t) || Tokens.isUnquotedText(t) || Tokens.isSubstitution(t)) {
|
||||
@ -289,8 +251,6 @@ final class ConfigDocumentParser {
|
||||
"Expecting a value but got wrong token: " + t));
|
||||
}
|
||||
|
||||
if (arrayCount != startingArrayCount)
|
||||
throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced array count");
|
||||
if (equalsCount != startingEqualsCount)
|
||||
throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced equals count");
|
||||
|
||||
@ -302,8 +262,8 @@ final class ConfigDocumentParser {
|
||||
if (Tokens.isValueWithType(token, ConfigValueType.STRING)) {
|
||||
return PathParser.parsePathNodeExpression(Collections.singletonList(token).iterator(), null);
|
||||
} else {
|
||||
throw parseError(addKeyName("Expecting close brace } or a field name here, got "
|
||||
+ token));
|
||||
throw parseError("Expecting close brace } or a field name here, got "
|
||||
+ token);
|
||||
}
|
||||
} else {
|
||||
List<Token> expression = new ArrayList<Token>();
|
||||
@ -314,8 +274,8 @@ final class ConfigDocumentParser {
|
||||
}
|
||||
|
||||
if (expression.isEmpty()) {
|
||||
throw parseError(addKeyName("expecting a close brace or a field name here, got "
|
||||
+ t));
|
||||
throw parseError("expecting a close brace or a field name here, got "
|
||||
+ t);
|
||||
}
|
||||
|
||||
putBack(t); // put back the token we ended with
|
||||
@ -351,19 +311,20 @@ final class ConfigDocumentParser {
|
||||
}
|
||||
|
||||
private ConfigNodeInclude parseInclude(ArrayList<AbstractConfigNode> children) {
|
||||
Token t = nextTokenIgnoringWhitespace(children);
|
||||
Token t = nextTokenCollectingWhitespace(children);
|
||||
|
||||
// we either have a quoted string or the "file()" syntax
|
||||
if (Tokens.isUnquotedText(t)) {
|
||||
// get foo(
|
||||
String kind = Tokens.getUnquotedText(t);
|
||||
|
||||
if (kind.equals("url(")) {
|
||||
|
||||
} else if (kind.equals("file(")) {
|
||||
|
||||
} else if (kind.equals("classpath(")) {
|
||||
String kindText = Tokens.getUnquotedText(t);
|
||||
ConfigIncludeKind kind;
|
||||
|
||||
if (kindText.equals("url(")) {
|
||||
kind = ConfigIncludeKind.URL;
|
||||
} else if (kindText.equals("file(")) {
|
||||
kind = ConfigIncludeKind.FILE;
|
||||
} else if (kindText.equals("classpath(")) {
|
||||
kind = ConfigIncludeKind.CLASSPATH;
|
||||
} else {
|
||||
throw parseError("expecting include parameter to be quoted filename, file(), classpath(), or url(). No spaces are allowed before the open paren. Not expecting: "
|
||||
+ t);
|
||||
@ -372,7 +333,7 @@ final class ConfigDocumentParser {
|
||||
children.add(new ConfigNodeSingleToken(t));
|
||||
|
||||
// skip space inside parens
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
|
||||
// quoted string
|
||||
String name;
|
||||
@ -382,20 +343,21 @@ final class ConfigDocumentParser {
|
||||
}
|
||||
children.add(new ConfigNodeSimpleValue(t));
|
||||
// skip space after string, inside parens
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
|
||||
if (Tokens.isUnquotedText(t) && Tokens.getUnquotedText(t).equals(")")) {
|
||||
// OK, close paren
|
||||
} else {
|
||||
throw parseError("expecting a close parentheses ')' here, not: " + t);
|
||||
}
|
||||
return new ConfigNodeInclude(children, kind);
|
||||
|
||||
} else if (Tokens.isValueWithType(t, ConfigValueType.STRING)) {
|
||||
children.add(new ConfigNodeSimpleValue(t));
|
||||
return new ConfigNodeInclude(children, ConfigIncludeKind.HEURISTIC);
|
||||
} else {
|
||||
throw parseError("include keyword is not followed by a quoted string, but by: " + t);
|
||||
}
|
||||
return new ConfigNodeInclude(children);
|
||||
}
|
||||
|
||||
private ConfigNodeComplexValue parseObject(boolean hadOpenCurly) {
|
||||
@ -410,7 +372,7 @@ final class ConfigDocumentParser {
|
||||
objectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY));
|
||||
|
||||
while (true) {
|
||||
Token t = nextTokenIgnoringWhitespace(objectNodes);
|
||||
Token t = nextTokenCollectingWhitespace(objectNodes);
|
||||
if (t == Tokens.CLOSE_CURLY) {
|
||||
if (flavor == ConfigSyntax.JSON && afterComma) {
|
||||
throw parseError(addQuoteSuggestion(t.toString(),
|
||||
@ -434,7 +396,7 @@ final class ConfigDocumentParser {
|
||||
Token keyToken = t;
|
||||
ConfigNodePath path = parseKey(keyToken);
|
||||
keyValueNodes.add(path);
|
||||
Token afterKey = nextTokenIgnoringWhitespace(keyValueNodes);
|
||||
Token afterKey = nextTokenCollectingWhitespace(keyValueNodes);
|
||||
boolean insideEquals = false;
|
||||
|
||||
Token valueToken;
|
||||
@ -458,7 +420,7 @@ final class ConfigDocumentParser {
|
||||
|
||||
nextValue = consolidateValues(keyValueNodes);
|
||||
if (nextValue == null) {
|
||||
nextValue = parseValue(nextTokenIgnoringWhitespace(keyValueNodes));
|
||||
nextValue = parseValue(nextTokenCollectingWhitespace(keyValueNodes));
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,7 +464,7 @@ final class ConfigDocumentParser {
|
||||
// continue looping
|
||||
afterComma = true;
|
||||
} else {
|
||||
t = nextTokenIgnoringWhitespace(objectNodes);
|
||||
t = nextTokenCollectingWhitespace(objectNodes);
|
||||
if (t == Tokens.CLOSE_CURLY) {
|
||||
if (!hadOpenCurly) {
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
@ -532,7 +494,6 @@ final class ConfigDocumentParser {
|
||||
ArrayList<AbstractConfigNode> children = new ArrayList<AbstractConfigNode>();
|
||||
children.add(new ConfigNodeSingleToken(Tokens.OPEN_SQUARE));
|
||||
// invoked just after the OPEN_SQUARE
|
||||
arrayCount += 1;
|
||||
Token t;
|
||||
|
||||
AbstractConfigNodeValue nextValue = consolidateValues(children);
|
||||
@ -540,11 +501,10 @@ final class ConfigDocumentParser {
|
||||
children.add(nextValue);
|
||||
nextValue = null;
|
||||
} else {
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
|
||||
// special-case the first element
|
||||
if (t == Tokens.CLOSE_SQUARE) {
|
||||
arrayCount -= 1;
|
||||
children.add(new ConfigNodeSingleToken(t));
|
||||
return new ConfigNodeArray(children);
|
||||
} else if (Tokens.isValue(t) || t == Tokens.OPEN_CURLY
|
||||
@ -554,11 +514,11 @@ final class ConfigDocumentParser {
|
||||
children.add(nextValue);
|
||||
nextValue = null;
|
||||
} else {
|
||||
throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: "
|
||||
throw parseError("List should have ] or a first element after the open [, instead had token: "
|
||||
+ t
|
||||
+ " (if you want "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)"));
|
||||
+ " to be part of a string value, then double-quote it)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -568,17 +528,16 @@ final class ConfigDocumentParser {
|
||||
if (checkElementSeparator(children)) {
|
||||
// comma (or newline equivalent) consumed
|
||||
} else {
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
if (t == Tokens.CLOSE_SQUARE) {
|
||||
arrayCount -= 1;
|
||||
children.add(new ConfigNodeSingleToken(t));
|
||||
return new ConfigNodeArray(children);
|
||||
} else {
|
||||
throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: "
|
||||
throw parseError("List should have ended with ] or had a comma, instead had token: "
|
||||
+ t
|
||||
+ " (if you want "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)"));
|
||||
+ " to be part of a string value, then double-quote it)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,7 +547,7 @@ final class ConfigDocumentParser {
|
||||
children.add(nextValue);
|
||||
nextValue = null;
|
||||
} else {
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
if (Tokens.isValue(t) || t == Tokens.OPEN_CURLY
|
||||
|| t == Tokens.OPEN_SQUARE || Tokens.isUnquotedText(t)
|
||||
|| Tokens.isSubstitution(t)) {
|
||||
@ -599,11 +558,11 @@ final class ConfigDocumentParser {
|
||||
// we allow one trailing comma
|
||||
putBack(t);
|
||||
} else {
|
||||
throw parseError(addKeyName("List should have had new element after a comma, instead had token: "
|
||||
throw parseError("List should have had new element after a comma, instead had token: "
|
||||
+ t
|
||||
+ " (if you want the comma or "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)"));
|
||||
+ " to be part of a string value, then double-quote it)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -619,7 +578,7 @@ final class ConfigDocumentParser {
|
||||
"token stream did not begin with START, had " + t);
|
||||
}
|
||||
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
AbstractConfigNode result = null;
|
||||
boolean missingCurly = false;
|
||||
if (t == Tokens.OPEN_CURLY || t == Tokens.OPEN_SQUARE) {
|
||||
@ -649,13 +608,13 @@ final class ConfigDocumentParser {
|
||||
} else {
|
||||
children.add(result);
|
||||
}
|
||||
t = nextTokenIgnoringWhitespace(children);
|
||||
t = nextTokenCollectingWhitespace(children);
|
||||
if (t == Tokens.END) {
|
||||
if (missingCurly) {
|
||||
// If there were no braces, the entire document should be treated as a single object
|
||||
return new ConfigNodeRoot(Collections.singletonList((AbstractConfigNode)new ConfigNodeObject(children)));
|
||||
return new ConfigNodeRoot(Collections.singletonList((AbstractConfigNode)new ConfigNodeObject(children)), baseOrigin);
|
||||
} else {
|
||||
return new ConfigNodeRoot(children);
|
||||
return new ConfigNodeRoot(children, baseOrigin);
|
||||
}
|
||||
} else {
|
||||
throw parseError("Document has trailing tokens after first object or array: "
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
enum ConfigIncludeKind {
|
||||
URL, FILE, CLASSPATH, HEURISTIC
|
||||
}
|
@ -3,21 +3,15 @@ package com.typesafe.config.impl;
|
||||
import java.util.Collection;
|
||||
|
||||
final class ConfigNodeInclude extends ConfigNodeComplexValue {
|
||||
ConfigNodeInclude(Collection<AbstractConfigNode> children) {
|
||||
final private ConfigIncludeKind kind;
|
||||
|
||||
ConfigNodeInclude(Collection<AbstractConfigNode> children, ConfigIncludeKind kind) {
|
||||
super(children);
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
protected String kind() {
|
||||
for (AbstractConfigNode n : children) {
|
||||
if (n instanceof ConfigNodeSingleToken) {
|
||||
Token t = ((ConfigNodeSingleToken) n).token();
|
||||
if (Tokens.isUnquotedText(t) && !t.tokenText().equals("include") &&
|
||||
t.tokenText().matches(".*\\w.*")) {
|
||||
return t.tokenText();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
protected ConfigIncludeKind kind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
protected String name() {
|
||||
|
@ -1,14 +1,18 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigSyntax;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
final class ConfigNodeRoot extends ConfigNodeComplexValue {
|
||||
ConfigNodeRoot(Collection<AbstractConfigNode> children) {
|
||||
final private ConfigOrigin origin;
|
||||
|
||||
ConfigNodeRoot(Collection<AbstractConfigNode> children, ConfigOrigin origin) {
|
||||
super(children);
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
protected ConfigNodeComplexValue value() {
|
||||
@ -26,10 +30,10 @@ final class ConfigNodeRoot extends ConfigNodeComplexValue {
|
||||
AbstractConfigNode node = childrenCopy.get(i);
|
||||
if (node instanceof ConfigNodeComplexValue) {
|
||||
if (node instanceof ConfigNodeArray) {
|
||||
throw new ConfigException.Generic("The ConfigDocument had an array at the root level, and values cannot be replaced inside an array.");
|
||||
throw new ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be replaced inside an array.");
|
||||
} else if (node instanceof ConfigNodeObject) {
|
||||
childrenCopy.set(i, ((ConfigNodeObject) node).setValueOnPath(desiredPath, value, flavor));
|
||||
return new ConfigNodeRoot(childrenCopy);
|
||||
return new ConfigNodeRoot(childrenCopy, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
*/
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -32,6 +34,6 @@ final class ConfigNodeSimpleValue extends AbstractConfigNodeValue {
|
||||
|
||||
return new ConfigReference(token.origin(), new SubstitutionExpression(path, optional));
|
||||
}
|
||||
return null;
|
||||
throw new ConfigException.BugOrBroken("ConfigNodeSimpleValue did not contain a valid value token");
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import com.typesafe.config.*;
|
||||
|
||||
@ -34,10 +33,7 @@ final class ConfigParser {
|
||||
final private ConfigSyntax flavor;
|
||||
final private ConfigOrigin baseOrigin;
|
||||
final private LinkedList<Path> pathStack;
|
||||
// this is the number of "equals" we are inside,
|
||||
// used to modify the error message to reflect that
|
||||
// someone may think this is .properties format.
|
||||
int equalsCount;
|
||||
|
||||
// the number of lists we are inside; this is used to detect the "cannot
|
||||
// generate a reference to a list element" problem, and once we fix that
|
||||
// problem we should be able to get rid of this variable.
|
||||
@ -52,7 +48,6 @@ final class ConfigParser {
|
||||
this.includer = includer;
|
||||
this.includeContext = includeContext;
|
||||
this.pathStack = new LinkedList<Path>();
|
||||
this.equalsCount = 0;
|
||||
this.arrayCount = 0;
|
||||
}
|
||||
|
||||
@ -62,7 +57,7 @@ final class ConfigParser {
|
||||
private AbstractConfigValue parseConcatenation(ConfigNodeConcatenation n) {
|
||||
// this trick is not done in JSON
|
||||
if (flavor == ConfigSyntax.JSON)
|
||||
return null;
|
||||
throw new ConfigException.BugOrBroken("Found a concatenation node in JSON");
|
||||
|
||||
// create only if we have value tokens
|
||||
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
|
||||
@ -102,7 +97,6 @@ final class ConfigParser {
|
||||
AbstractConfigValue v;
|
||||
|
||||
int startingArrayCount = arrayCount;
|
||||
int startingEqualsCount = equalsCount;
|
||||
|
||||
if (n instanceof ConfigNodeSimpleValue) {
|
||||
v = ((ConfigNodeSimpleValue) n).value();
|
||||
@ -123,8 +117,6 @@ final class ConfigParser {
|
||||
|
||||
if (arrayCount != startingArrayCount)
|
||||
throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced array count");
|
||||
if (equalsCount != startingEqualsCount)
|
||||
throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced equals count");
|
||||
|
||||
return v;
|
||||
}
|
||||
@ -167,9 +159,8 @@ final class ConfigParser {
|
||||
|
||||
private void parseInclude(Map<String, AbstractConfigValue> values, ConfigNodeInclude n) {
|
||||
AbstractConfigObject obj;
|
||||
|
||||
if (n.kind() != null) {
|
||||
if (n.kind().equals("url(")) {
|
||||
switch (n.kind()) {
|
||||
case URL:
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(n.name());
|
||||
@ -177,17 +168,24 @@ final class ConfigParser {
|
||||
throw parseError("include url() specifies an invalid URL: " + n.name(), e);
|
||||
}
|
||||
obj = (AbstractConfigObject) includer.includeURL(includeContext, url);
|
||||
} else if (n.kind().equals("file(")) {
|
||||
break;
|
||||
|
||||
case FILE:
|
||||
obj = (AbstractConfigObject) includer.includeFile(includeContext,
|
||||
new File(n.name()));
|
||||
} else if (n.kind().equals("classpath(")) {
|
||||
break;
|
||||
|
||||
case CLASSPATH:
|
||||
obj = (AbstractConfigObject) includer.includeResources(includeContext, n.name());
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken("should not be reached");
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
|
||||
case HEURISTIC:
|
||||
obj = (AbstractConfigObject) includer
|
||||
.include(includeContext, n.name());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ConfigException.BugOrBroken("should not be reached");
|
||||
}
|
||||
|
||||
// we really should make this work, but for now throwing an
|
||||
@ -267,7 +265,6 @@ final class ConfigParser {
|
||||
|
||||
if (((ConfigNodeField) node).separator() == Tokens.EQUALS) {
|
||||
insideEquals = true;
|
||||
equalsCount += 1;
|
||||
}
|
||||
|
||||
valueNode = ((ConfigNodeField) node).value();
|
||||
@ -315,9 +312,6 @@ final class ConfigParser {
|
||||
}
|
||||
|
||||
pathStack.pop();
|
||||
if (insideEquals) {
|
||||
equalsCount -= 1;
|
||||
}
|
||||
|
||||
String key = path.first();
|
||||
Path remaining = path.remainder();
|
||||
|
@ -213,7 +213,7 @@ public abstract class Parseable implements ConfigParseable {
|
||||
if (finalOptions.getAllowMissing()) {
|
||||
ArrayList<AbstractConfigNode> children = new ArrayList<AbstractConfigNode>();
|
||||
children.add(new ConfigNodeObject(new ArrayList<AbstractConfigNode>()));
|
||||
return new SimpleConfigDocument(new ConfigNodeRoot(children), finalOptions);
|
||||
return new SimpleConfigDocument(new ConfigNodeRoot(children, origin), finalOptions);
|
||||
} else {
|
||||
trace("exception loading " + origin.description() + ": " + e.getClass().getName()
|
||||
+ ": " + e.getMessage());
|
||||
@ -256,9 +256,8 @@ public abstract class Parseable implements ConfigParseable {
|
||||
return PropertiesParser.parse(reader, origin);
|
||||
} else {
|
||||
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax());
|
||||
ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, finalOptions);
|
||||
ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, origin, finalOptions);
|
||||
return ConfigParser.parse(document, origin, finalOptions, includeContext());
|
||||
//return Parser.parse(tokens, origin, finalOptions, includeContext());
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,7 +291,7 @@ public abstract class Parseable implements ConfigParseable {
|
||||
private ConfigDocument rawParseDocument(Reader reader, ConfigOrigin origin,
|
||||
ConfigParseOptions finalOptions) throws IOException {
|
||||
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax());
|
||||
return new SimpleConfigDocument(ConfigDocumentParser.parse(tokens, finalOptions), finalOptions);
|
||||
return new SimpleConfigDocument(ConfigDocumentParser.parse(tokens, origin, finalOptions), finalOptions);
|
||||
}
|
||||
|
||||
public ConfigObject parse() {
|
||||
|
@ -21,7 +21,7 @@ final class SimpleConfigDocument implements ConfigDocument {
|
||||
SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("single value parsing");
|
||||
StringReader reader = new StringReader(newValue);
|
||||
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, parseOptions.getSyntax());
|
||||
AbstractConfigNodeValue parsedValue = ConfigDocumentParser.parseValue(tokens, parseOptions);
|
||||
AbstractConfigNodeValue parsedValue = ConfigDocumentParser.parseValue(tokens, origin, parseOptions);
|
||||
reader.close();
|
||||
|
||||
return new SimpleConfigDocument(configNodeTree.setValue(path, parsedValue, parseOptions.getSyntax()), parseOptions);
|
||||
|
@ -7,14 +7,14 @@ import org.junit.Test
|
||||
class ConfigDocumentParserTest extends TestUtils {
|
||||
|
||||
private def parseTest(origText: String) {
|
||||
val node = ConfigDocumentParser.parse(tokenize(origText))
|
||||
val node = ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertEquals(origText, node.render())
|
||||
}
|
||||
|
||||
private def parseJSONFailuresTest(origText: String, containsMessage: String) {
|
||||
var exceptionThrown = false
|
||||
try {
|
||||
ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
exceptionThrown = true
|
||||
@ -26,32 +26,32 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
|
||||
private def parseSimpleValueTest(origText: String, finalText: String = null) {
|
||||
val expectedRenderedText = if (finalText == null) origText else finalText
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertEquals(expectedRenderedText, node.render())
|
||||
assertTrue(node.isInstanceOf[AbstractConfigNodeValue])
|
||||
|
||||
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
assertEquals(expectedRenderedText, nodeJSON.render())
|
||||
assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue])
|
||||
}
|
||||
|
||||
private def parseComplexValueTest(origText: String) {
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertEquals(origText, node.render())
|
||||
assertTrue(node.isInstanceOf[AbstractConfigNodeValue])
|
||||
|
||||
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
assertEquals(origText, nodeJSON.render())
|
||||
assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue])
|
||||
}
|
||||
|
||||
private def parseSingleValueInvalidJSONTest(origText: String, containsMessage: String) {
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertEquals(origText, node.render())
|
||||
|
||||
var exceptionThrown = false
|
||||
try {
|
||||
ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
exceptionThrown = true
|
||||
@ -64,7 +64,7 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
private def parseLeadingTrailingFailure(toReplace: String) {
|
||||
var exceptionThrown = false
|
||||
try {
|
||||
ConfigDocumentParser.parseValue(tokenize(toReplace), ConfigParseOptions.defaults())
|
||||
ConfigDocumentParser.parseValue(tokenize(toReplace), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
exceptionThrown = true
|
||||
@ -202,7 +202,7 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
]
|
||||
}
|
||||
"""
|
||||
val node = ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
val node = ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
assertEquals(origText, node.render())
|
||||
}
|
||||
|
||||
@ -256,14 +256,14 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
|
||||
// Check that concatenations are handled by CONF parsing
|
||||
var origText = "123 456 unquotedtext abc"
|
||||
var node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
|
||||
var node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertEquals(origText, node.render())
|
||||
|
||||
// Check that concatenations in JSON will throw an error
|
||||
origText = "123 456 789"
|
||||
var exceptionThrown = false
|
||||
try {
|
||||
node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
exceptionThrown = true
|
||||
@ -294,7 +294,7 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
val origText = "123 456 789"
|
||||
var exceptionThrown = false
|
||||
try {
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
exceptionThrown = true
|
||||
@ -303,4 +303,14 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
}
|
||||
assertTrue(exceptionThrown)
|
||||
}
|
||||
|
||||
@Test
|
||||
def parseEmptyDocument {
|
||||
val node = ConfigDocumentParser.parse(tokenize(""), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertTrue(node.value().isInstanceOf[ConfigNodeObject])
|
||||
assertTrue(node.value().children().isEmpty())
|
||||
|
||||
val node2 = ConfigDocumentParser.parse(tokenize("#comment\n#comment\n\n"), fakeOrigin(), ConfigParseOptions.defaults())
|
||||
assertTrue(node2.value().isInstanceOf[ConfigNodeObject])
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user