diff --git a/config/src/main/java/com/typesafe/config/ConfigDocument.java b/config/src/main/java/com/typesafe/config/ConfigDocument.java index a5261ae4..b81f1b5b 100644 --- a/config/src/main/java/com/typesafe/config/ConfigDocument.java +++ b/config/src/main/java/com/typesafe/config/ConfigDocument.java @@ -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 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 4559a9da..0aa3b453 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java @@ -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 tokens, ConfigParseOptions options) { - ParseContext context = new ParseContext(options.getSyntax(), tokens); + static ConfigNodeRoot parse(Iterator 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 tokens) { - ParseContext context = new ParseContext(ConfigSyntax.CONF, tokens); - return context.parse(); - } - - static AbstractConfigNodeValue parseValue(Iterator tokens, ConfigParseOptions options) { - ParseContext context = new ParseContext(options.getSyntax(), tokens); + static AbstractConfigNodeValue parseValue(Iterator 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 buffer; final private Iterator tokens; final private ConfigSyntax flavor; - final private LinkedList 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 tokens) { + ParseContext(ConfigSyntax flavor, ConfigOrigin origin, Iterator tokens) { lineNumber = 1; buffer = new Stack(); this.tokens = tokens; this.flavor = flavor; - this.pathStack = new LinkedList(); 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 nodes) { + private Token nextTokenCollectingWhitespace(Collection 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 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 expression = new ArrayList(); @@ -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 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 children = new ArrayList(); 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: " diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigIncludeKind.java b/config/src/main/java/com/typesafe/config/impl/ConfigIncludeKind.java new file mode 100644 index 00000000..65d596fb --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/ConfigIncludeKind.java @@ -0,0 +1,5 @@ +package com.typesafe.config.impl; + +enum ConfigIncludeKind { + URL, FILE, CLASSPATH, HEURISTIC +} diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeInclude.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeInclude.java index 811b6c71..04b303ac 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeInclude.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeInclude.java @@ -3,21 +3,15 @@ package com.typesafe.config.impl; import java.util.Collection; final class ConfigNodeInclude extends ConfigNodeComplexValue { - ConfigNodeInclude(Collection children) { + final private ConfigIncludeKind kind; + + ConfigNodeInclude(Collection 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() { diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeRoot.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeRoot.java index 112b1588..34421855 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeRoot.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeRoot.java @@ -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 children) { + final private ConfigOrigin origin; + + ConfigNodeRoot(Collection 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); } } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeSimpleValue.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeSimpleValue.java index 2232e87f..5165b20e 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeSimpleValue.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeSimpleValue.java @@ -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"); } } 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 58155a6e..32f98477 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java @@ -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 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(); - 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 values = new ArrayList(); @@ -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 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(); diff --git a/config/src/main/java/com/typesafe/config/impl/Parseable.java b/config/src/main/java/com/typesafe/config/impl/Parseable.java index edc2fbf3..6d52dfc8 100644 --- a/config/src/main/java/com/typesafe/config/impl/Parseable.java +++ b/config/src/main/java/com/typesafe/config/impl/Parseable.java @@ -213,7 +213,7 @@ public abstract class Parseable implements ConfigParseable { if (finalOptions.getAllowMissing()) { ArrayList children = new ArrayList(); children.add(new ConfigNodeObject(new ArrayList())); - 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 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 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() { diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java index d3cffd61..04ae4e9c 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java @@ -21,7 +21,7 @@ final class SimpleConfigDocument implements ConfigDocument { SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("single value parsing"); StringReader reader = new StringReader(newValue); Iterator 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); 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 95eda0de..a0e6bf9f 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala @@ -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]) + } } diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala index 3a395e37..861821e2 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala @@ -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