diff --git a/src/main/java/com/typesafe/config/ConfigObject.java b/src/main/java/com/typesafe/config/ConfigObject.java index 8ad71b99..21da5ae1 100644 --- a/src/main/java/com/typesafe/config/ConfigObject.java +++ b/src/main/java/com/typesafe/config/ConfigObject.java @@ -13,11 +13,9 @@ import java.util.Set; * a type that the value can't be converted to. ConfigException.Null is a * subclass of ConfigException.WrongType thrown if the value is null. The "path" * parameters for all the getters have periods between the key names, so the - * path "a.b.c" looks for key c in object b in object a in the root object. - * - * - * TODO If you need to look up a key with a period in its name, there isn't a - * way to do it right now. + * path "a.b.c" looks for key c in object b in object a in the root object. (The + * syntax for paths is the same as in ${} substitution expressions in config + * files, sometimes double quotes are needed around special characters.) * * TODO add OrNull variants of all these getters? Or better to avoid convenience * API for that? diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index 67db777b..391fb3bb 100644 --- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -112,37 +112,53 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements } static private AbstractConfigValue resolve(AbstractConfigObject self, - String path, + String pathExpression, ConfigValueType expected, ConfigTransformer transformer, String originalPath) { - String key = ConfigUtil.firstElement(path); - String next = ConfigUtil.otherElements(path); + Path path = Path.newPath(pathExpression); + return resolve(self, path, expected, transformer, originalPath); + } + + static private AbstractConfigValue resolveKey(AbstractConfigObject self, + String key, ConfigValueType expected, + ConfigTransformer transformer, String originalPath) { + AbstractConfigValue v = self.peek(key); + if (v == null) + throw new ConfigException.Missing(originalPath); + + if (expected != null && transformer != null) + v = transformer.transform(v, expected); + + if (v.valueType() == ConfigValueType.NULL) + throw new ConfigException.Null(v.origin(), originalPath, + expected != null ? expected.name() : null); + else if (expected != null && v.valueType() != expected) + throw new ConfigException.WrongType(v.origin(), originalPath, + expected.name(), v.valueType().name()); + else + return v; + } + + static private AbstractConfigValue resolve(AbstractConfigObject self, + Path path, ConfigValueType expected, ConfigTransformer transformer, + String originalPath) { + String key = path.first(); + Path next = path.remainder(); if (next == null) { - AbstractConfigValue v = self.peek(key); - if (v == null) - throw new ConfigException.Missing(originalPath); - - if (expected != null && transformer != null) - v = transformer.transform(v, expected); - - if (v.valueType() == ConfigValueType.NULL) - throw new ConfigException.Null(v.origin(), originalPath, - expected != null ? expected.name() : null); - else if (expected != null && v.valueType() != expected) - throw new ConfigException.WrongType(v.origin(), originalPath, - expected.name(), v.valueType().name()); - else - return v; + return resolveKey(self, key, expected, transformer, originalPath); } else { - AbstractConfigObject o = self.getObject(key); + AbstractConfigObject o = (AbstractConfigObject) resolveKey(self, + key, ConfigValueType.OBJECT, transformer, originalPath); assert (o != null); // missing was supposed to throw return resolve(o, next, expected, transformer, originalPath); } } - AbstractConfigValue resolve(String path, ConfigValueType expected, + AbstractConfigValue resolve(String pathExpression, + ConfigValueType expected, String originalPath) { - return resolve(this, path, expected, transformer, originalPath); + return resolve(this, pathExpression, expected, transformer, + originalPath); } /** diff --git a/src/main/java/com/typesafe/config/impl/ConfigUtil.java b/src/main/java/com/typesafe/config/impl/ConfigUtil.java index 19673519..39318fd6 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigUtil.java +++ b/src/main/java/com/typesafe/config/impl/ConfigUtil.java @@ -1,54 +1,7 @@ package com.typesafe.config.impl; -import com.typesafe.config.ConfigException; final class ConfigUtil { - static void verifyPath(String path) { - if (path.startsWith(".")) - throw new ConfigException.BadPath(path, "Path starts with '.'"); - if (path.endsWith(".")) - throw new ConfigException.BadPath(path, "Path ends with '.'"); - if (path.contains("..")) - throw new ConfigException.BadPath(path, - "Path contains '..' (empty element)"); - } - - static String firstElement(String path) { - verifyPath(path); - int i = path.indexOf('.'); - if (i < 0) - return path; - else - return path.substring(0, i); - } - - static String otherElements(String path) { - verifyPath(path); - int i = path.indexOf('.'); - if (i < 0) - return null; - else - return path.substring(i + 1); - } - - static String lastElement(String path) { - verifyPath(path); - int i = path.lastIndexOf('.'); - if (i < 0) - return path; - else - return path.substring(i + 1); - } - - static String exceptLastElement(String path) { - verifyPath(path); - int i = path.lastIndexOf('.'); - if (i < 0) - return null; - else - return path.substring(0, i); - } - static boolean equalsHandlingNull(Object a, Object b) { if (a == null && b != null) return false; diff --git a/src/main/java/com/typesafe/config/impl/Loader.java b/src/main/java/com/typesafe/config/impl/Loader.java index 3168a85b..a3000c48 100644 --- a/src/main/java/com/typesafe/config/impl/Loader.java +++ b/src/main/java/com/typesafe/config/impl/Loader.java @@ -79,6 +79,34 @@ final class Loader { } } + static void verifyPath(String path) { + if (path.startsWith(".")) + throw new ConfigException.BadPath(path, "Path starts with '.'"); + if (path.endsWith(".")) + throw new ConfigException.BadPath(path, "Path ends with '.'"); + if (path.contains("..")) + throw new ConfigException.BadPath(path, + "Path contains '..' (empty element)"); + } + + static String lastElement(String path) { + verifyPath(path); + int i = path.lastIndexOf('.'); + if (i < 0) + return path; + else + return path.substring(i + 1); + } + + static String exceptLastElement(String path) { + verifyPath(path); + int i = path.lastIndexOf('.'); + if (i < 0) + return null; + else + return path.substring(0, i); + } + static AbstractConfigObject fromProperties(String originPrefix, Properties props) { Map<String, Map<String, AbstractConfigValue>> scopes = new HashMap<String, Map<String, AbstractConfigValue>>(); @@ -88,8 +116,8 @@ final class Loader { if (o instanceof String) { try { String path = (String) o; - String last = ConfigUtil.lastElement(path); - String exceptLast = ConfigUtil.exceptLastElement(path); + String last = lastElement(path); + String exceptLast = exceptLastElement(path); if (exceptLast == null) exceptLast = ""; Map<String, AbstractConfigValue> scope = scopes @@ -116,7 +144,7 @@ final class Loader { // put everything in its parent, ensuring all parents exist for (String path : childPaths) { - String parentPath = ConfigUtil.exceptLastElement(path); + String parentPath = exceptLastElement(path); if (parentPath == null) parentPath = ""; @@ -132,7 +160,7 @@ final class Loader { AbstractConfigObject o = new SimpleConfigObject( new SimpleConfigOrigin(originPrefix + " " + path), null, scopes.get(path)); - String basename = ConfigUtil.lastElement(path); + String basename = lastElement(path); parent.put(basename, o); } diff --git a/src/main/java/com/typesafe/config/impl/PathBuilder.java b/src/main/java/com/typesafe/config/impl/PathBuilder.java index 5dd8a4fe..0f6a3d53 100644 --- a/src/main/java/com/typesafe/config/impl/PathBuilder.java +++ b/src/main/java/com/typesafe/config/impl/PathBuilder.java @@ -19,24 +19,6 @@ final class PathBuilder { "Adding to PathBuilder after getting result"); } - void appendPath(String path) { - checkCanAppend(); - ConfigUtil.verifyPath(path); - - String next = ConfigUtil.firstElement(path); - String remainder = ConfigUtil.otherElements(path); - - while (next != null) { - keys.push(next); - if (remainder != null) { - next = ConfigUtil.firstElement(remainder); - remainder = ConfigUtil.otherElements(remainder); - } else { - next = null; - } - } - } - void appendKey(String key) { checkCanAppend(); diff --git a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index c63afa45..4245cc1d 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -63,6 +63,7 @@ class ConfParserTest extends TestUtils { } } } + // also parse with the standalone path parser and be sure the // outcome is the same val shouldBeSame = Parser.parsePath(s) @@ -93,6 +94,7 @@ class ConfParserTest extends TestUtils { assertEquals(path(""), parsePath("\"\"\"\"")) assertEquals(path("a", ""), parsePath("a.\"\"\"\"")) assertEquals(path("", "b"), parsePath("\"\"\"\".b")) + assertEquals(path("", "", ""), parsePath(""" "".""."" """)) for (invalid <- Seq("a.", ".b", "a..b", "a${b}c", "\"\".", ".\"\"")) { try { diff --git a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index bd0b2070..88b19b23 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -370,7 +370,7 @@ class ConfigTest extends TestUtils { } @Test - def test02WeirdPaths() { + def test02SubstitutionsWithWeirdPaths() { val conf = Config.load("test02") assertEquals(42, conf.getInt("42_a")) @@ -380,4 +380,16 @@ class ConfigTest extends TestUtils { assertEquals(57, conf.getInt("57_b")) assertEquals(103, conf.getInt("103_a")) } + + @Test + def test02UseWeirdPathsWithConfigObject() { + val conf = Config.load("test02") + + // we're checking that the getters in ConfigObject support + // these weird path expressions + assertEquals(42, conf.getInt(""" "".""."" """)) + assertEquals(57, conf.getInt("a.b.c")) + assertEquals(57, conf.getInt(""" "a"."b"."c" """)) + assertEquals(103, conf.getInt(""" "a.b.c" """)) + } }