diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 42e4df0c..942697b9 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -168,10 +168,22 @@ public interface Config extends ConfigMergeable { * * @param options * resolve options - * @return the resolved Config + * @return the resolved Config (may be only partially resolved if options are set to allow unresolved) */ Config resolve(ConfigResolveOptions options); + /** + * Checks whether the config is completely resolved. After a successful call to + * {@link Config#resolve()} it will be completely resolved, but after calling + * {@link Config#resolve(ConfigResolveOptions)} with allowUnresolved set + * in the options, it may or may not be completely resolved. A newly-loaded config + * may or may not be completely resolved depending on whether there were substitutions + * present in the file. + * + * @return true if there are no unresolved substitutions remaining in this configuration. + */ + boolean isResolved(); + /** * Validates this config against a reference config, throwing an exception * if it is invalid. The purpose of this method is to "fail early" with a diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java index d3be5cf4..e7c8f695 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -815,6 +815,11 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { } } + @Override + public boolean isResolved() { + return root().resolveStatus() == ResolveStatus.RESOLVED; + } + @Override public void checkValid(Config reference, String... restrictToPaths) { SimpleConfig ref = (SimpleConfig) reference; diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index 157680a1..26b85388 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -1104,10 +1104,23 @@ class ConfigTest extends TestUtils { } } + @Test + def isResolvedWorks() { + val resolved = ConfigFactory.parseString("foo = 1") + assertTrue("config with no substitutions starts as resolved", resolved.isResolved) + val unresolved = ConfigFactory.parseString("foo = ${a}, a=42") + assertFalse("config with substitutions starts as not resolved", unresolved.isResolved) + val resolved2 = unresolved.resolve() + assertTrue("after resolution, config is now resolved", resolved2.isResolved) + } + @Test def allowUnresolvedDoesAllowUnresolved() { val values = ConfigFactory.parseString("{ foo = 1, bar = 2, m = 3, n = 4}") + assertTrue("config with no substitutions starts as resolved", values.isResolved) val unresolved = ConfigFactory.parseString("a = ${foo}, b = ${bar}, c { x = ${m}, y = ${n} }, alwaysResolveable=${alwaysValue}, alwaysValue=42") + assertFalse("config with substitutions starts as not resolved", unresolved.isResolved) + // resolve() by default throws with unresolveable substs intercept[ConfigException.UnresolvedSubstitution] { unresolved.resolve(ConfigResolveOptions.defaults()) @@ -1123,10 +1136,13 @@ class ConfigTest extends TestUtils { for (k <- Seq("a", "b", "c.x", "c.y")) { intercept[ConfigException.NotResolved] { allowedUnresolved.getInt(k) } } + // and the partially-resolved thing is not resolved + assertFalse("partially-resolved object is not resolved", allowedUnresolved.isResolved) // and given the values for the resolve, we should be able to val resolved = allowedUnresolved.withFallback(values).resolve() for (kv <- Seq("a" -> 1, "b" -> 2, "c.x" -> 3, "c.y" -> 4)) { assertEquals(kv._2, resolved.getInt(kv._1)) } + assertTrue("fully resolved object is resolved", resolved.isResolved) } }