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:
Preben Ingvaldsen 2015-03-27 10:52:59 -07:00
parent 13cb4785b9
commit 74070b44cc
11 changed files with 126 additions and 158 deletions

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package com.typesafe.config.impl;
enum ConfigIncludeKind {
URL, FILE, CLASSPATH, HEURISTIC
}

View File

@ -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() {

View File

@ -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);
}
}
}

View File

@ -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");
}
}

View File

@ -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 {
break;
case HEURISTIC:
obj = (AbstractConfigObject) includer
.include(includeContext, n.name());
break;
default:
throw new ConfigException.BugOrBroken("should not be reached");
}
} else {
obj = (AbstractConfigObject) includer
.include(includeContext, n.name());
}
// 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();

View File

@ -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() {

View File

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

View File

@ -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,12 +64,12 @@ 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
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("The value from setValue cannot have leading or trailing newlines, whitespace, or comments"))
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("The value from setValue cannot have leading or trailing newlines, whitespace, or comments"))
}
assertTrue(exceptionThrown)
}
@ -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])
}
}

View File

@ -213,7 +213,7 @@ class ConfigDocumentTest extends TestUtils {
assertTrue(exceptionThrown)
}
@Test
@Test
def configDocumentJSONReplaceWithConcatenationFailure {
// Attempting a replace on a ConfigDocument parsed from JSON with a concatenation will
// fail