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