From 247236c6f1fd7d7d0348c64a5460c553ac84e338 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Fri, 3 Jan 2014 11:02:45 -0500 Subject: [PATCH] add Config.resolveWith() This allows replacing substitutions with values which are not merged into the Config. Fixes #95 --- .../main/java/com/typesafe/config/Config.java | 40 +++++++++++++++++++ .../typesafe/config/impl/SimpleConfig.java | 13 +++++- .../com/typesafe/config/impl/ConfigTest.scala | 33 ++++++++++++--- 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 942697b9..7ed754cc 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -184,6 +184,46 @@ public interface Config extends ConfigMergeable { */ boolean isResolved(); + /** + * Like {@link Config#resolve()} except that substitution values are looked + * up in the given source, rather than in this instance. This is a + * special-purpose method which doesn't make sense to use in most cases; + * it's only needed if you're constructing some sort of app-specific custom + * approach to configuration. The more usual approach if you have a source + * of substitution values would be to merge that source into your config + * stack using {@link Config#withFallback} and then resolve. + *

+ * Note that this method does NOT look in this instance for substitution + * values. If you want to do that, you could either merge this instance into + * your value source using {@link Config#withFallback}, or you could resolve + * multiple times with multiple sources (using + * {@link ConfigResolveOptions#setAllowUnresolved(boolean)} so the partial + * resolves don't fail). + * + * @param source + * configuration to pull values from + * @return an immutable object with substitutions resolved + * @throws ConfigException.UnresolvedSubstitution + * if any substitutions refer to paths which are not in the + * source + * @throws ConfigException + * some other config exception if there are other problems + */ + Config resolveWith(Config source); + + /** + * Like {@link Config#resolveWith(Config)} but allows you to specify + * non-default options. + * + * @param source + * source configuration to pull values from + * @param options + * resolve options + * @return the resolved Config (may be only partially resolved + * if options are set to allow unresolved) + */ + Config resolveWith(Config source, ConfigResolveOptions options); + /** * 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 e7c8f695..f54dcd18 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -57,7 +57,17 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { @Override public SimpleConfig resolve(ConfigResolveOptions options) { - AbstractConfigValue resolved = ResolveContext.resolve(object, object, options); + return resolveWith(this, options); + } + + @Override + public SimpleConfig resolveWith(Config source) { + return resolveWith(source, ConfigResolveOptions.defaults()); + } + + @Override + public SimpleConfig resolveWith(Config source, ConfigResolveOptions options) { + AbstractConfigValue resolved = ResolveContext.resolve(object, ((SimpleConfig) source).object, options); if (resolved == object) return this; @@ -65,7 +75,6 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return new SimpleConfig((AbstractConfigObject) resolved); } - @Override public boolean hasPath(String pathExpression) { Path path = Path.newPath(pathExpression); 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 26b85388..8c58a510 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -1138,11 +1138,34 @@ class ConfigTest extends TestUtils { } // 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)) + + // scope "val resolved" + { + // 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) } - assertTrue("fully resolved object is resolved", resolved.isResolved) + + // we should also be able to use resolveWith + { + val resolved = allowedUnresolved.resolveWith(values) + 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) + } + } + + @Test + def resolveWithWorks(): Unit = { + // the a=42 is present here to be sure it gets ignored when we resolveWith + val unresolved = ConfigFactory.parseString("foo = ${a}, a = 42") + assertEquals(42, unresolved.resolve().getInt("foo")) + val source = ConfigFactory.parseString("a = 43") + val resolved = unresolved.resolveWith(source) + assertEquals(43, resolved.getInt("foo")) } }