use full path parser for ConfigObject getters

This commit is contained in:
Havoc Pennington 2011-11-10 15:36:41 -05:00
parent cfff60131e
commit a70bb89c2a
7 changed files with 87 additions and 96 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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