diff --git a/src/main/java/com/typesafe/config/impl/Parser.java b/src/main/java/com/typesafe/config/impl/Parser.java index de4175d3..054e4531 100644 --- a/src/main/java/com/typesafe/config/impl/Parser.java +++ b/src/main/java/com/typesafe/config/impl/Parser.java @@ -214,7 +214,7 @@ final class Parser { // now save substitution List expression = Tokens .getSubstitutionPathExpression(valueToken); - Path path = parsePathExpression(expression, + Path path = parsePathExpression(expression.iterator(), Tokens.getSubstitutionOrigin(valueToken)); minimized.add(path); } else { @@ -447,13 +447,14 @@ final class Parser { } } - private static Path parsePathExpression(List expression, + private static Path parsePathExpression(Iterator expression, ConfigOrigin origin) { // each builder in "buf" is an element in the path. List buf = new ArrayList(); buf.add(new Element("", false)); - for (Token t : expression) { + while (expression.hasNext()) { + Token t = expression.next(); if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { AbstractConfigValue v = Tokens.getValue(t); // this is a quoted string; so any periods @@ -461,6 +462,11 @@ final class Parser { String s = v.transformToString(); addPathText(buf, true, s); + } else if (t == Tokens.END) { + // ignore this; when parsing a file, it should not happen + // since we're parsing a token list rather than the main + // token iterator, and when parsing a path expression from the + // API, it's expected to have an END. } else { // any periods outside of a quoted string count as // separators @@ -501,4 +507,14 @@ final class Parser { return pb.result(); } + + static ConfigOrigin apiOrigin = new SimpleConfigOrigin("path parameter"); + + static Path parsePath(String path) { + StringReader reader = new StringReader(path); + Iterator tokens = Tokenizer.tokenize(apiOrigin, reader); + tokens.next(); // drop START + + return parsePathExpression(tokens, apiOrigin); + } } diff --git a/src/main/java/com/typesafe/config/impl/Path.java b/src/main/java/com/typesafe/config/impl/Path.java index f256ef64..9f571fe1 100644 --- a/src/main/java/com/typesafe/config/impl/Path.java +++ b/src/main/java/com/typesafe/config/impl/Path.java @@ -101,8 +101,6 @@ final class Path { } static Path newPath(String path) { - PathBuilder pb = new PathBuilder(); - pb.appendPath(path); - return pb.result(); + return Parser.parsePath(path); } } diff --git a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index db7c4748..c63afa45 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -51,8 +51,10 @@ class ConfParserTest extends TestUtils { } private def parsePath(s: String): Path = { + // parse first by wrapping into a whole document and using + // the regular parser. val tree = parseWithoutResolving("[${" + s + "}]") - tree match { + val result = tree match { case list: ConfigList => list.asJavaList().get(0) match { case subst: ConfigSubstitution => @@ -61,6 +63,12 @@ class ConfParserTest extends TestUtils { } } } + // also parse with the standalone path parser and be sure the + // outcome is the same + val shouldBeSame = Parser.parsePath(s) + assertEquals(result, shouldBeSame) + + result } @Test diff --git a/src/test/scala/com/typesafe/config/impl/PathTest.scala b/src/test/scala/com/typesafe/config/impl/PathTest.scala index cded51ec..6ed775de 100644 --- a/src/test/scala/com/typesafe/config/impl/PathTest.scala +++ b/src/test/scala/com/typesafe/config/impl/PathTest.scala @@ -9,10 +9,14 @@ class PathTest extends TestUtils { def pathEquality() { // note: foo.bar is a single key here val a = Path.newKey("foo.bar") + // check that newKey worked + assertEquals(path("foo.bar"), a); val sameAsA = Path.newKey("foo.bar") val differentKey = Path.newKey("hello") // here foo.bar is two elements val twoElements = Path.newPath("foo.bar") + // check that newPath worked + assertEquals(path("foo", "bar"), twoElements); val sameAsTwoElements = Path.newPath("foo.bar") checkEqualObjects(a, a) @@ -24,18 +28,18 @@ class PathTest extends TestUtils { @Test def pathToString() { - assertEquals("Path(foo)", Path.newPath("foo").toString()) - assertEquals("Path(foo.bar)", Path.newPath("foo.bar").toString()) - assertEquals("Path(foo.\"bar*\")", Path.newPath("foo.bar*").toString()) - assertEquals("Path(\"foo.bar\")", Path.newKey("foo.bar").toString()) + assertEquals("Path(foo)", path("foo").toString()) + assertEquals("Path(foo.bar)", path("foo", "bar").toString()) + assertEquals("Path(foo.\"bar*\")", path("foo", "bar*").toString()) + assertEquals("Path(\"foo.bar\")", path("foo.bar").toString()) } @Test def pathRender() { - assertEquals("foo", Path.newPath("foo").render()) - assertEquals("foo.bar", Path.newPath("foo.bar").render()) - assertEquals("foo.\"bar*\"", Path.newPath("foo.bar*").render()) - assertEquals("\"foo.bar\"", Path.newKey("foo.bar").render()) - assertEquals("foo bar", Path.newKey("foo bar").render()) + assertEquals("foo", path("foo").render()) + assertEquals("foo.bar", path("foo", "bar").render()) + assertEquals("foo.\"bar*\"", path("foo", "bar*").render()) + assertEquals("\"foo.bar\"", path("foo.bar").render()) + assertEquals("foo bar", path("foo bar").render()) } } diff --git a/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/src/test/scala/com/typesafe/config/impl/TestUtils.scala index 11567d46..3fdd443c 100644 --- a/src/test/scala/com/typesafe/config/impl/TestUtils.scala +++ b/src/test/scala/com/typesafe/config/impl/TestUtils.scala @@ -288,5 +288,8 @@ abstract trait TestUtils { tokenize(s).asScala.toList } + // this is importantly NOT using Path.newPath, which relies on + // the parser; in the test suite we are often testing the parser, + // so we don't want to use the parser to build the expected result. def path(elements: String*) = new Path(elements: _*) }