diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 629b107b..1fbb6d60 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -506,4 +506,25 @@ public interface Config extends ConfigMergeable { * @return a copy of the config minus the specified path */ Config withoutPath(String path); + + /** + * Places the config inside another {@code Config} at the given path. + * + * @param path + * path to store this config at. + * @return a {@code Config} instance containing this config at the given + * path. + */ + Config atPath(String path); + + /** + * Places the config inside a {@code Config} at the given key. See also + * atPath(). + * + * @param key + * key to store this config at. + * @return a {@code Config} instance containing this config at the given + * key. + */ + Config atKey(String key); } diff --git a/config/src/main/java/com/typesafe/config/ConfigValue.java b/config/src/main/java/com/typesafe/config/ConfigValue.java index e0f7bef2..514336c7 100644 --- a/config/src/main/java/com/typesafe/config/ConfigValue.java +++ b/config/src/main/java/com/typesafe/config/ConfigValue.java @@ -76,7 +76,7 @@ public interface ConfigValue extends ConfigMergeable { * HOCON-specific features (such as comments), the rendering will be valid * JSON. If you enable HOCON-only features such as comments, the rendering * will not be valid JSON. - * + * * @param options * the rendering options * @return the rendered value @@ -85,4 +85,25 @@ public interface ConfigValue extends ConfigMergeable { @Override ConfigValue withFallback(ConfigMergeable other); + + /** + * Places the value inside a {@code Config} at the given path. See also + * atKey(). + * + * @param path + * path to store this value at. + * @return a {@code Config} instance containing this value at the given + * path. + */ + Config atPath(String path); + + /** + * Places the value inside a {@code Config} at the given key. See also + * atPath(). + * + * @param key + * key to store this value at. + * @return a {@code Config} instance containing this value at the given key. + */ + Config atKey(String key); } diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index 4b31808c..834ae269 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -7,7 +7,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigOrigin; @@ -327,4 +329,28 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue { String transformToString() { return null; } + + SimpleConfig atKey(ConfigOrigin origin, String key) { + Map<String, AbstractConfigValue> m = Collections.singletonMap(key, this); + return (new SimpleConfigObject(origin, m)).toConfig(); + } + + @Override + public SimpleConfig atKey(String key) { + return atKey(SimpleConfigOrigin.newSimple("atKey(" + key + ")"), key); + } + + @Override + public Config atPath(String pathExpression) { + SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("atPath(" + pathExpression + ")"); + Path path = Path.newPath(pathExpression); + Path parent = path.parent(); + SimpleConfig result = atKey(origin, path.last()); + while (parent != null) { + String key = parent.last(); + result = result.atKey(origin, key); + parent = parent.parent(); + } + return result; + } } 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 f5aad19c..a30eedd6 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -846,6 +846,20 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return new SimpleConfig(root().withoutPath(path)); } + SimpleConfig atKey(ConfigOrigin origin, String key) { + return root().atKey(origin, key); + } + + @Override + public SimpleConfig atKey(String key) { + return root().atKey(key); + } + + @Override + public Config atPath(String path) { + return root().atPath(path); + } + // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index 35ea1c68..04b981cb 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -14,6 +14,7 @@ import com.typesafe.config.ConfigList import com.typesafe.config.ConfigException import com.typesafe.config.ConfigValueType import com.typesafe.config.ConfigOrigin +import com.typesafe.config.ConfigValueFactory class ConfigValueTest extends TestUtils { @@ -751,6 +752,42 @@ class ConfigValueTest extends TestUtils { assertEquals(ResolveStatus.RESOLVED, obj.withoutKey("a").withoutKey("b").resolveStatus()) } + @Test + def atPathWorksOneElement() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = v.atPath("a") + assertEquals(parseConfig("a=42"), config) + assertTrue(config.getValue("a") eq v) + assertTrue(config.origin.description.contains("atPath")) + } + + @Test + def atPathWorksTwoElements() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = v.atPath("a.b") + assertEquals(parseConfig("a.b=42"), config) + assertTrue(config.getValue("a.b") eq v) + assertTrue(config.origin.description.contains("atPath")) + } + + @Test + def atPathWorksFourElements() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = v.atPath("a.b.c.d") + assertEquals(parseConfig("a.b.c.d=42"), config) + assertTrue(config.getValue("a.b.c.d") eq v) + assertTrue(config.origin.description.contains("atPath")) + } + + @Test + def atKeyWorks() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = v.atKey("a") + assertEquals(parseConfig("a=42"), config) + assertTrue(config.getValue("a") eq v) + assertTrue(config.origin.description.contains("atKey")) + } + @Test def configOriginsInSerialization() { import scala.collection.JavaConverters._