From f81ab20caa4e6e0c81766bba3ffeff9c0181ff01 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Tue, 15 Nov 2011 23:30:03 -0500 Subject: [PATCH] implement ConfigObject.hasPath() --- .../com/typesafe/config/ConfigObject.java | 28 +++++++++++++-- .../config/impl/AbstractConfigObject.java | 7 ++++ .../config/impl/ConfigValueTest.scala | 34 +++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/typesafe/config/ConfigObject.java b/src/main/java/com/typesafe/config/ConfigObject.java index cd9d327f..cca81a7b 100644 --- a/src/main/java/com/typesafe/config/ConfigObject.java +++ b/src/main/java/com/typesafe/config/ConfigObject.java @@ -18,10 +18,13 @@ import java.util.Map; * files, sometimes double quotes are needed around special characters.) * * ConfigObject implements the standard Java Map interface, but the mutator - * methods all throw UnsupportedOperationException. + * methods all throw UnsupportedOperationException. This Map is immutable. * - * TODO add OrNull variants of all these getters? Or better to avoid convenience - * API for that? + * The Map may contain null values, which will have ConfigValue.valueType() == + * ConfigValueType.NULL. When using methods from the Map interface, such as + * get() or containsKey(), these null ConfigValue will be visible. But hasPath() + * returns false for null values, and getInt() etc. throw ConfigException.Null + * for null values. */ public interface ConfigObject extends ConfigValue, Map { @@ -38,6 +41,25 @@ public interface ConfigObject extends ConfigValue, Map { @Override ConfigObject withFallbacks(ConfigValue... others); + /** + * Checks whether a value is present and non-null at the given path. This + * differs in two ways from containsKey(): it looks for a path expression, + * not a key; and it returns false for null values, while containsKey() + * returns true indicating that the object contains a null value for the + * key. + * + * If a path exists according to hasPath(), then getValue() will never throw + * an exception. However, the typed getters, such as getInt(), will still + * throw if the value is not convertible to the requested type. + * + * @param path + * the path expression + * @return true if a non-null value is present at the path + * @throws ConfigException.BadPath + * if the path expression is invalid + */ + boolean hasPath(String path); + boolean getBoolean(String path); Number getNumber(String path); diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index 90d42c30..ed0b027b 100644 --- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -121,6 +121,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements return ConfigValueType.OBJECT; } + @Override + public boolean hasPath(String pathExpression) { + Path path = Path.newPath(pathExpression); + ConfigValue peeked = peekPath(path, null, 0, null); + return peeked != null && peeked.valueType() != ConfigValueType.NULL; + } + @Override AbstractConfigObject transformed(ConfigTransformer newTransformer) { if (newTransformer != transformer) diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index d3c26546..4ba5bff2 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -371,4 +371,38 @@ class ConfigValueTest extends TestUtils { // merge three assertEquals("merge of a,b,c", m(o("a", false), o("b", false), o("c", false))) } + + @Test + def hasPathWorks() { + val empty = parseObject("{}") + + assertFalse(empty.hasPath("foo")) + + val obj = parseObject("a=null, b.c.d=11, foo=bar") + + // returns true for the non-null values + assertTrue(obj.hasPath("foo")) + assertTrue(obj.hasPath("b.c.d")) + assertTrue(obj.hasPath("b.c")) + assertTrue(obj.hasPath("b")) + + // hasPath() is false for null values but containsKey is true + assertEquals(nullValue(), obj.get("a")) + assertTrue(obj.containsKey("a")) + assertFalse(obj.hasPath("a")) + + // false for totally absent values + assertFalse(obj.containsKey("notinhere")) + assertFalse(obj.hasPath("notinhere")) + + // throws proper exceptions + intercept[ConfigException.BadPath] { + empty.hasPath("a.") + } + + intercept[ConfigException.BadPath] { + empty.hasPath("..") + } + + } }