diff --git a/src/main/java/com/typesafe/config/Config.java b/src/main/java/com/typesafe/config/Config.java index 5efa1a4a..292f5fc0 100644 --- a/src/main/java/com/typesafe/config/Config.java +++ b/src/main/java/com/typesafe/config/Config.java @@ -90,6 +90,58 @@ public interface Config extends ConfigMergeable { @Override ConfigObject toValue(); + /** + * Returns a replacement config with all substitutions (the + * <code>${foo.bar}</code> syntax, see <a + * href="https://github.com/havocp/config/blob/master/HOCON.md">the + * spec</a>) resolved. Substitutions are looked up using this + * <code>Config</code> as the root object, that is, a substitution + * <code>${foo.bar}</code> will be replaced with the result of + * <code>getValue("foo.bar")</code>. + * + * <p> + * This method uses {@link ConfigResolveOptions#defaults()}, there is + * another variant {@link Config#resolve(ConfigResolveOptions)} which lets + * you specify non-default options. + * + * <p> + * A given {@link Config} must be resolved before using it to retrieve + * config values, but ideally should be resolved one time for your entire + * stack of fallbacks (see {@link Config#withFallback}). Otherwise, some + * substitutions that could have resolved with all fallbacks available may + * not resolve, which will be a user-visible oddity. + * + * <p> + * <code>resolve()</code> should be invoked on root config objects, rather + * than on a subtree (a subtree is the result of something like + * <code>config.getConfig("foo")</code>). The problem with + * <code>resolve()</code> on a subtree is that substitutions are relative to + * the root of the config and the subtree will have no way to get values + * from the root. For example, if you did + * <code>config.getConfig("foo").resolve()</code> on the below config file, + * it would not work: + * + * <pre> + * common-value = 10 + * foo { + * whatever = ${common-value} + * } + * </pre> + * + * @return an immutable object with substitutions resolved + */ + Config resolve(); + + /** + * Like {@link Config#resolve()} but allows you to specify non-default + * options. + * + * @param options + * resolve options + * @return the resolved <code>Config</code> + */ + Config resolve(ConfigResolveOptions options); + /** * Checks whether a value is present and non-null at the given path. This * differs in two ways from {@code Map.containsKey()} as implemented by @@ -213,7 +265,7 @@ public interface Config extends ConfigMergeable { * Gets the value at the path as an unwrapped Java boxed value ( * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and * so on - see {@link ConfigValue#unwrapped()}). - * + * * @param path * path expression * @return the unwrapped value at the requested path diff --git a/src/main/java/com/typesafe/config/ConfigFactory.java b/src/main/java/com/typesafe/config/ConfigFactory.java index 82a71dce..544a3156 100644 --- a/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/src/main/java/com/typesafe/config/ConfigFactory.java @@ -53,31 +53,31 @@ public final class ConfigFactory { * the configuration "domain" * @return configuration for the requested root path */ - public static ConfigRoot load(String rootPath) { + public static Config load(String rootPath) { return loadWithoutResolving(rootPath).resolve(); } - public static ConfigRoot load(String rootPath, + public static Config load(String rootPath, ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { return loadWithoutResolving(rootPath, parseOptions).resolve( resolveOptions); } /** - * Like load() but does not resolve the object, so you can go ahead and add + * Like load() but does not resolve the config, so you can go ahead and add * more fallbacks and stuff and have them seen by substitutions when you do - * call {@link ConfigRoot#resolve}. + * call {@link Config#resolve()}. * * @param rootPath * @return configuration for the requested root path */ - public static ConfigRoot loadWithoutResolving(String rootPath) { + public static Config loadWithoutResolving(String rootPath) { return loadWithoutResolving(rootPath, ConfigParseOptions.defaults()); } - public static ConfigRoot loadWithoutResolving(String rootPath, + public static Config loadWithoutResolving(String rootPath, ConfigParseOptions options) { - ConfigRoot system = systemPropertiesRoot(rootPath); + Config system = ConfigImpl.systemPropertiesWithPrefix(rootPath); Config mainFiles = parseResourcesForPath(rootPath, options); Config referenceFiles = parseResourcesForPath(rootPath + ".reference", @@ -86,26 +86,14 @@ public final class ConfigFactory { return system.withFallback(mainFiles).withFallback(referenceFiles); } - public static ConfigRoot emptyRoot(String rootPath) { - return emptyRoot(rootPath, null); - } - public static Config empty() { return empty(null); } - public static ConfigRoot emptyRoot(String rootPath, String originDescription) { - return ConfigImpl.emptyRoot(rootPath, originDescription); - } - public static Config empty(String originDescription) { return ConfigImpl.emptyConfig(originDescription); } - public static ConfigRoot systemPropertiesRoot(String rootPath) { - return ConfigImpl.systemPropertiesRoot(rootPath); - } - public static Config systemProperties() { return ConfigImpl.systemPropertiesAsConfig(); } @@ -177,7 +165,7 @@ public final class ConfigFactory { /** * Same behavior as {@link #parseFileAnySyntax(File,ConfigParseOptions)} but * for classpath resources instead. - * + * * @param klass * @param resourceBasename * @param options diff --git a/src/main/java/com/typesafe/config/ConfigRoot.java b/src/main/java/com/typesafe/config/ConfigRoot.java deleted file mode 100644 index 9d285c48..00000000 --- a/src/main/java/com/typesafe/config/ConfigRoot.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (C) 2011 Typesafe Inc. <http://typesafe.com> - */ -package com.typesafe.config; - -/** - * Subtype of {@link Config} which is a root rather than nested object and - * supports {@link ConfigRoot#resolve resolving substitutions}. - * - * <p> - * <em>Do not implement this interface</em>; it should only be implemented by - * the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigRoot extends Config { - /** - * Returns a replacement root object with all substitutions (the - * <code>${foo.bar}</code> syntax, see <a - * href="https://github.com/havocp/config/blob/master/HOCON.md">the - * spec</a>) resolved. Substitutions are looked up in this root object. A - * {@link Config} must be resolved before you can use it. This method uses - * {@link ConfigResolveOptions#defaults()}. - * - * @return an immutable object with substitutions resolved - */ - ConfigRoot resolve(); - - /** - * Like {@link ConfigRoot#resolve()} but allows you to specify options. - * - * @param options - * resolve options - * @return the resolved root config - */ - ConfigRoot resolve(ConfigResolveOptions options); - - @Override - ConfigRoot withFallback(ConfigMergeable fallback); - - /** - * Gets the global app name that this root represents. - * - * @return the app's root config path - */ - String rootPath(); -} diff --git a/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 8c2bd01e..acbde153 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -19,7 +19,6 @@ import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseable; -import com.typesafe.config.ConfigRoot; import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigValue; @@ -139,13 +138,6 @@ public class ConfigImpl { return fromBasename(source, basename.getPath(), baseOptions); } - /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ - public static ConfigRoot emptyRoot(String rootPath, String originDescription) { - String desc = originDescription != null ? originDescription : rootPath; - return emptyObject(desc).toConfig().asRoot( - Path.newPath(rootPath)); - } - static AbstractConfigObject emptyObject(String originDescription) { ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin( originDescription) : null; @@ -291,13 +283,12 @@ public class ConfigImpl { } /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ - public static ConfigRoot systemPropertiesRoot(String rootPath) { - Path path = Parser.parsePath(rootPath); + /* FIXME this is broken and will go away when Config.load() doesn't use it. */ + public static Config systemPropertiesWithPrefix(String rootPath) { try { - return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath) - .asRoot(path); + return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath); } catch (ConfigException.Missing e) { - return emptyObject("system properties").toConfig().asRoot(path); + return emptyObject("system properties").toConfig(); } } diff --git a/src/main/java/com/typesafe/config/impl/RootConfig.java b/src/main/java/com/typesafe/config/impl/RootConfig.java deleted file mode 100644 index 723b9fad..00000000 --- a/src/main/java/com/typesafe/config/impl/RootConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2011 Typesafe Inc. <http://typesafe.com> - */ -package com.typesafe.config.impl; - -import com.typesafe.config.ConfigMergeable; -import com.typesafe.config.ConfigResolveOptions; -import com.typesafe.config.ConfigRoot; - -final class RootConfig extends SimpleConfig implements ConfigRoot { - - final private Path rootPath; - - RootConfig(AbstractConfigObject underlying, Path rootPath) { - super(underlying); - this.rootPath = rootPath; - } - - @Override - protected RootConfig asRoot(AbstractConfigObject underlying, - Path newRootPath) { - if (newRootPath.equals(this.rootPath)) - return this; - else - return new RootConfig(underlying, newRootPath); - } - - @Override - public RootConfig resolve() { - return resolve(ConfigResolveOptions.defaults()); - } - - @Override - public RootConfig resolve(ConfigResolveOptions options) { - // if the object is already resolved then we should end up returning - // "this" here, since asRoot() should return this if the path - // is unchanged. - AbstractConfigObject resolved = resolvedObject(options); - return newRootIfObjectChanged(this, resolved); - } - - @Override - public RootConfig withFallback(ConfigMergeable value) { - // this can return "this" if the withFallback does nothing - return newRootIfObjectChanged(this, super.withFallback(value).toObject()); - } - - Path rootPathObject() { - return rootPath; - } - - @Override - public String rootPath() { - return rootPath.render(); - } - - @Override - public String toString() { - return "Root" + super.toString(); - } -} diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/src/main/java/com/typesafe/config/impl/SimpleConfig.java index d64ce462..d3517c09 100644 --- a/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -43,35 +43,22 @@ class SimpleConfig implements Config { return object.origin(); } - /** - * Returns a version of this config that implements the ConfigRoot - * interface. - * - * @return a config root - */ - RootConfig asRoot(Path rootPath) { - return asRoot(object, rootPath); + @Override + public SimpleConfig resolve() { + return resolve(ConfigResolveOptions.defaults()); } - // RootConfig overrides this to avoid a new object on unchanged path. - protected RootConfig asRoot(AbstractConfigObject underlying, - Path newRootPath) { - return new RootConfig(underlying, newRootPath); - } - - static protected RootConfig newRootIfObjectChanged(RootConfig self, AbstractConfigObject underlying) { - if (underlying == self.object) - return self; - else - return new RootConfig(underlying, self.rootPathObject()); - } - - protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) { + @Override + public SimpleConfig resolve(ConfigResolveOptions options) { AbstractConfigValue resolved = SubstitutionResolver.resolve(object, object, options); - return (AbstractConfigObject) resolved; + if (resolved == object) + return this; + else + return new SimpleConfig((AbstractConfigObject) resolved); } + @Override public boolean hasPath(String pathExpression) { Path path = Path.newPath(pathExpression); diff --git a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index 2116bdc4..768a2451 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -499,10 +499,6 @@ class ConfigTest extends TestUtils { def ignoredMergesDoNothing() { val conf = parseConfig("{ a : 1 }") testIgnoredMergesDoNothing(conf) - - // ConfigRoot mode uses a little different codepath - val root = conf.asRoot(path("whatever")) - testIgnoredMergesDoNothing(root) } @Test diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index 61228eaa..a12a6c8d 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -101,12 +101,6 @@ class ConfigValueTest extends TestUtils { // configs are not equal to objects checkNotEqualObjects(a, a.toConfig()) checkNotEqualObjects(b, b.toConfig()) - - // configs are equal to the same config as a root - val root = config.asRoot(path("foo")) - checkEqualObjects(config, root) - checkNotEqualObjects(b.toConfig(), root) - checkNotEqualObjects(c.toConfig(), root) } @Test diff --git a/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala index 4408a6ae..985cddf8 100644 --- a/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -87,8 +87,6 @@ class PublicApiTest extends TestUtils { assertEquals("empty config", ConfigFactory.empty().origin().description()) assertTrue(ConfigFactory.empty("foo").isEmpty()) assertEquals("foo", ConfigFactory.empty("foo").origin().description()) - assertTrue(ConfigFactory.emptyRoot("foo.bar").isEmpty()) - assertEquals("foo.bar", ConfigFactory.emptyRoot("foo.bar").origin().description()) } private val defaultValueDesc = "hardcoded value";