Keep tokens in Path

Save the list of Tokens from which a Path was created when a
Path is parsed from a string in Parser.parsePath. Change
ConfigNodeKey to store a Path instead of a token.
This commit is contained in:
Preben Ingvaldsen 2015-03-11 11:27:28 -07:00
parent e695543bf1
commit cce8c204ac
7 changed files with 76 additions and 42 deletions
config/src

View File

@ -33,7 +33,7 @@ final class ConfigNodeComplexValue extends AbstractConfigNodeValue {
continue;
}
node = (ConfigNodeKeyValue)children.get(i);
key = Path.newPath(node.key().render());
key = node.key().value();
if (key.equals(desiredPath)) {
if (!replaced) {
childrenCopy.set(i, node.replaceValue(value));
@ -67,7 +67,7 @@ final class ConfigNodeComplexValue extends AbstractConfigNodeValue {
ArrayList<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(children);
ArrayList<AbstractConfigNode> newNodes = new ArrayList();
newNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null)));
newNodes.add(new ConfigNodeKey(Tokens.newUnquotedText(null, desiredPath.render())));
newNodes.add(new ConfigNodeKey(desiredPath));
newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " ")));
newNodes.add(new ConfigNodeSingleToken(Tokens.COLON));
newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " ")));

View File

@ -1,15 +1,18 @@
package com.typesafe.config.impl;
import java.util.Collection;
import java.util.Collections;
final class ConfigNodeKey extends AbstractConfigNode {
Token token;
ConfigNodeKey(Token t) {
token = t;
final private Path key;
ConfigNodeKey(Path key) {
this.key = key;
}
protected Collection<Token> tokens() {
return Collections.singletonList(token);
return key.tokens();
}
protected Path value() {
return key;
}
}

View File

@ -1063,6 +1063,7 @@ final class Parser {
private static Path parsePathExpression(Iterator<Token> expression,
ConfigOrigin origin, String originalText) {
// each builder in "buf" is an element in the path.
ArrayList<Token> pathTokens = new ArrayList<Token>();
List<Element> buf = new ArrayList<Element>();
buf.add(new Element("", false));
@ -1073,6 +1074,7 @@ final class Parser {
while (expression.hasNext()) {
Token t = expression.next();
pathTokens.add(t);
// Ignore all IgnoredWhitespace tokens
if (Tokens.isIgnoredWhitespace(t))
@ -1119,7 +1121,7 @@ final class Parser {
}
}
PathBuilder pb = new PathBuilder();
PathBuilder pb = new PathBuilder(pathTokens);
for (Element e : buf) {
if (e.sb.length() == 0 && !e.canBeEmpty) {
throw new ConfigException.BadPath(
@ -1192,8 +1194,10 @@ final class Parser {
private static Path fastPathBuild(Path tail, String s, int end) {
// lastIndexOf takes last index it should look at, end - 1 not end
int splitAt = s.lastIndexOf('.', end - 1);
ArrayList<Token> tokens = new ArrayList<Token>();
tokens.add(Tokens.newUnquotedText(null, s));
// this works even if splitAt is -1; then we start the substring at 0
Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail);
Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail, tokens);
if (splitAt < 0) {
return withOneMoreElement;
} else {

View File

@ -3,6 +3,8 @@
*/
package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@ -13,12 +15,22 @@ final class Path {
final private String first;
final private Path remainder;
// We only need to keep track of this for top-level paths created with
// parsePath, so this will be empty or null for all other cases
final private ArrayList<Token> tokens;
Path(String first, Path remainder) {
this(first, remainder, new ArrayList<Token>());
}
Path(String first, Path remainder, Collection<Token> tokens) {
this.first = first;
this.remainder = remainder;
this.tokens = new ArrayList<Token>(tokens);
}
Path(String... elements) {
this.tokens = null;
if (elements.length == 0)
throw new ConfigException.BugOrBroken("empty path");
this.first = elements[0];
@ -40,6 +52,7 @@ final class Path {
// append all the paths in the iterator together into one path
Path(Iterator<Path> i) {
this.tokens = null;
if (!i.hasNext())
throw new ConfigException.BugOrBroken("empty path");
@ -204,6 +217,10 @@ final class Path {
}
}
protected Collection<Token> tokens() {
return tokens;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();

View File

@ -3,6 +3,8 @@
*/
package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Stack;
import com.typesafe.config.ConfigException;
@ -12,8 +14,17 @@ final class PathBuilder {
final private Stack<String> keys;
private Path result;
// the tokens only matter for top-level paths created with parsePath, in all
// other cases this will be empty
final private ArrayList<Token> tokens;
PathBuilder() {
this(new ArrayList<Token>());
}
PathBuilder(Collection<Token> tokens) {
keys = new Stack<String>();
this.tokens = new ArrayList<Token>(tokens);
}
private void checkCanAppend() {
@ -51,7 +62,7 @@ final class PathBuilder {
Path remainder = null;
while (!keys.isEmpty()) {
String key = keys.pop();
remainder = new Path(key, remainder);
remainder = new Path(key, remainder, tokens);
}
result = remainder;
}

View File

@ -11,9 +11,9 @@ class ConfigNodeTest extends TestUtils {
assertEquals(node.render(), token.tokenText())
}
private def keyNodeTest(token: Token) {
val node = configNodeKey(token)
assertEquals(node.render(), token.tokenText())
private def keyNodeTest(path: String) {
val node = configNodeKey(path)
assertEquals(path, node.render())
}
private def simpleValueNodeTest(token: Token) {
@ -32,21 +32,21 @@ class ConfigNodeTest extends TestUtils {
assertEquals(newValue.render(), newKeyValNode.value().render())
}
private def topLevelValueReplaceTest(value: AbstractConfigNodeValue, newValue: AbstractConfigNodeValue, key: Token = tokenString("foo")) {
private def topLevelValueReplaceTest(value: AbstractConfigNodeValue, newValue: AbstractConfigNodeValue, key: String = "foo") {
val complexNodeChildren = List(nodeOpenBrace,
nodeKeyValuePair(nodeWhitespace(" "), configNodeKey(key),value, nodeWhitespace(" ")),
nodeCloseBrace)
val complexNode = configNodeComplexValue(complexNodeChildren)
val newNode = complexNode.setValueOnPath(Path.newPath(key.tokenText()), newValue)
val origText = "{ " + key.tokenText() + " : " + value.render() + " }"
val finalText = "{ " + key.tokenText() + " : " + newValue.render() + " }"
val newNode = complexNode.setValueOnPath(Path.newPath(key), newValue)
val origText = "{ " + key + " : " + value.render() + " }"
val finalText = "{ " + key + " : " + newValue.render() + " }"
assertEquals(origText, complexNode.render())
assertEquals(finalText, newNode.render())
}
private def replaceDuplicatesTest(value1: AbstractConfigNodeValue, value2: AbstractConfigNodeValue, value3: AbstractConfigNodeValue) {
val key = nodeUnquotedKey("foo")
val key = configNodeKey("foo")
val keyValPair1 = nodeKeyValuePair(key, value1)
val keyValPair2 = nodeKeyValuePair(key, value2)
val keyValPair3 = nodeKeyValuePair(key, value3)
@ -59,7 +59,7 @@ class ConfigNodeTest extends TestUtils {
}
private def nonExistentPathTest(value: AbstractConfigNodeValue) {
val node = configNodeComplexValue(List(nodeKeyValuePair(nodeUnquotedKey("bar"), nodeInt(15))))
val node = configNodeComplexValue(List(nodeKeyValuePair(configNodeKey("bar"), nodeInt(15))))
assertEquals("bar : 15", node.render())
val newNode = node.setValueOnPath(Path.newPath("foo"), value)
val finalText = "bar : 15\nfoo : " + value.render() + "\n"
@ -89,8 +89,8 @@ class ConfigNodeTest extends TestUtils {
@Test
def createConfigNodeSetting() {
//Ensure a ConfigNodeSetting can handle the normal key types
keyNodeTest(tokenUnquoted("foo"))
keyNodeTest(tokenString("Hello I am a key how are you today"))
keyNodeTest("foo")
keyNodeTest("\"Hello I am a key how are you today\"")
}
@Test
@ -112,12 +112,12 @@ class ConfigNodeTest extends TestUtils {
@Test
def createConfigNodeKeyValue() {
// Supports Quoted and Unquoted keys
keyValueNodeTest(nodeQuotedKey("abc"), nodeInt(123), nodeLine(1), nodeInt(245))
keyValueNodeTest(nodeUnquotedKey("abc"), nodeInt(123), nodeLine(1), nodeInt(245))
keyValueNodeTest(configNodeKey("\"abc\""), nodeInt(123), nodeLine(1), nodeInt(245))
keyValueNodeTest(configNodeKey("abc"), nodeInt(123), nodeLine(1), nodeInt(245))
// Can replace value with values of different types
keyValueNodeTest(nodeQuotedKey("abc"), nodeInt(123), nodeLine(1), nodeString("I am a string"))
keyValueNodeTest(nodeQuotedKey("abc"), nodeInt(123), nodeLine(1), configNodeComplexValue(List(nodeOpenBrace, nodeCloseBrace)))
keyValueNodeTest(configNodeKey("\"abc\""), nodeInt(123), nodeLine(1), nodeString("I am a string"))
keyValueNodeTest(configNodeKey("\"abc\""), nodeInt(123), nodeLine(1), configNodeComplexValue(List(nodeOpenBrace, nodeCloseBrace)))
}
@Test
@ -142,7 +142,7 @@ class ConfigNodeTest extends TestUtils {
topLevelValueReplaceTest(array, configNodeComplexValue(List(nodeOpenBrace, nodeCloseBrace)))
//Ensure maps can be replaced
val nestedMap = configNodeComplexValue(List(nodeOpenBrace, nodeUnquotedKey("abc"),
val nestedMap = configNodeComplexValue(List(nodeOpenBrace, configNodeKey("abc"),
nodeColon, configNodeSimpleValue(tokenString("a string")),
nodeCloseBrace))
topLevelValueReplaceTest(nestedMap, nodeInt(10))
@ -152,7 +152,7 @@ class ConfigNodeTest extends TestUtils {
topLevelValueReplaceTest(nestedMap, configNodeComplexValue(List(nodeOpenBrace, nodeCloseBrace)))
//Ensure a key with format "a.b" will be properly replaced
topLevelValueReplaceTest(nodeInt(10), nestedMap, tokenUnquoted("foo.bar"))
topLevelValueReplaceTest(nodeInt(10), nestedMap, "foo.bar")
}
@Test
@ -170,7 +170,7 @@ class ConfigNodeTest extends TestUtils {
def addNonExistentPaths() {
nonExistentPathTest(nodeInt(10))
nonExistentPathTest(configNodeComplexValue(List(nodeOpenBracket, nodeInt(15), nodeCloseBracket)))
nonExistentPathTest(configNodeComplexValue(List(nodeOpenBrace, nodeKeyValuePair(nodeUnquotedKey("foo"), nodeDouble(3.14), nodeSpace))))
nonExistentPathTest(configNodeComplexValue(List(nodeOpenBrace, nodeKeyValuePair(configNodeKey("foo"), nodeDouble(3.14), nodeSpace))))
}
@Test
@ -179,23 +179,23 @@ class ConfigNodeTest extends TestUtils {
val origText = "foo : bar\nbaz : {\n\t\"abc.def\" : 123\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" +
"def : \"this is a string\"\n\t\tghi : ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi : 52\nbaz.abc.ghi : 53\n}"
val lowestLevelMap = configNodeComplexValue(List(nodeOpenBrace, nodeLine(6),
nodeKeyValuePair(nodeWhitespace("\t\t"), nodeUnquotedKey("def"), configNodeSimpleValue(tokenString("this is a string")), nodeLine(7)),
nodeKeyValuePair(nodeWhitespace("\t\t"), nodeUnquotedKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b")), nodeLine(8)),
nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("def"), configNodeSimpleValue(tokenString("this is a string")), nodeLine(7)),
nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b")), nodeLine(8)),
nodeWhitespace("\t"), nodeCloseBrace))
val higherLevelMap = configNodeComplexValue(List(nodeOpenBrace, nodeLine(2),
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey(tokenString("abc.def")), configNodeSimpleValue(tokenInt(123)), nodeLine(3)),
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("\"abc.def\""), configNodeSimpleValue(tokenInt(123)), nodeLine(3)),
nodeWhitespace("\t"), configNodeBasic(tokenCommentDoubleSlash("This is a comment about the below setting")),
nodeLine(4), nodeLine(5),
nodeKeyValuePair(nodeWhitespace("\t"), nodeUnquotedKey("abc"), lowestLevelMap, nodeLine(9)), nodeWhitespace(""),
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("abc"), lowestLevelMap, nodeLine(9)), nodeWhitespace(""),
nodeCloseBrace))
val origNode = configNodeComplexValue(List(nodeKeyValuePair(nodeUnquotedKey("foo"), configNodeSimpleValue(tokenUnquoted("bar")), nodeLine(1)),
nodeKeyValuePair(nodeUnquotedKey("baz"), higherLevelMap, nodeLine(10)),
nodeKeyValuePair(nodeUnquotedKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52)), nodeLine(11)),
nodeKeyValuePair(nodeUnquotedKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53)), nodeLine(12)),
val origNode = configNodeComplexValue(List(nodeKeyValuePair(configNodeKey("foo"), configNodeSimpleValue(tokenUnquoted("bar")), nodeLine(1)),
nodeKeyValuePair(configNodeKey("baz"), higherLevelMap, nodeLine(10)),
nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52)), nodeLine(11)),
nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53)), nodeLine(12)),
nodeCloseBrace))
assertEquals(origText, origNode.render())
val finalText = "foo : bar\nbaz : {\n\t\"abc.def\" : true\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" +
"def : false\n\t}\n}\nbaz.abc.ghi : randomunquotedString\n}\nbaz.abc.this.does.not.exist : doesnotexist\n"
"def : false\n\t}\n}\nbaz.abc.ghi : randomunquotedString\n}\nbaz.abc.\"this.does.not.exist@@@+$#\".end : doesnotexist\n"
//Can replace settings in nested maps
// Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths
@ -206,7 +206,7 @@ class ConfigNodeTest extends TestUtils {
newNode = newNode.setValueOnPath(Path.newPath("baz.abc.ghi"), configNodeSimpleValue(tokenUnquoted("randomunquotedString")))
// Missing paths are added to the top level if they don't appear anywhere, including in nested maps
newNode = newNode.setValueOnPath(Path.newPath("baz.abc.this.does.not.exist"), configNodeSimpleValue(tokenUnquoted("doesnotexist")))
newNode = newNode.setValueOnPath(Path.newPath("baz.abc.\"this.does.not.exist@@@+$#\".end"), configNodeSimpleValue(tokenUnquoted("doesnotexist")))
// The above operations cause the resultant map to be rendered properly
assertEquals(finalText, newNode.render())

View File

@ -667,8 +667,9 @@ abstract trait TestUtils {
new ConfigNodeSimpleValue(value)
}
def configNodeKey(value: Token) = {
new ConfigNodeKey(value)
def configNodeKey(path: String) = {
val parsedPath = Parser.parsePath(path)
new ConfigNodeKey(parsedPath)
}
def configNodeBasic(value: Token) = {
@ -688,8 +689,6 @@ abstract trait TestUtils {
def nodeComma = new ConfigNodeSingleToken(Tokens.COMMA)
def nodeLine(line: Integer) = new ConfigNodeSingleToken(tokenLine(line))
def nodeWhitespace(whitespace: String) = new ConfigNodeSingleToken(tokenWhitespace(whitespace))
def nodeQuotedKey(key: String) = configNodeKey(tokenString(key))
def nodeUnquotedKey(key: String) = configNodeKey(tokenUnquoted(key))
def nodeKeyValuePair(key: ConfigNodeKey, value: AbstractConfigNodeValue) = {
val nodes = List(key, nodeSpace, nodeColon, nodeSpace, value)
new ConfigNodeKeyValue(nodes.asJavaCollection)