diff --git a/NEWS.md b/NEWS.md index 389f7e9f..b9c77c3f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,10 @@ where one was an empty string, and now you can't. As far as I know, the empty string was always worthless in this case and can just be removed. +- added methods atPath() and atKey() to ConfigValue, to wrap + the value into a Config +- added method withValue() to Config and ConfigObject, + to add a value at a given path or key # 0.4.1: May 22, 2012 diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 1fbb6d60..e53b0849 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -527,4 +527,18 @@ public interface Config extends ConfigMergeable { * key. */ Config atKey(String key); + + /** + * Returns a {@code Config} based on this one, but with the given path set + * to the given value. Does not modify this instance (since it's immutable). + * If the path already has a value, that value is replaced. To remove a + * value, use withoutPath(). + * + * @param path + * path to add + * @param value + * value at the new path + * @return the new instance with the new map entry + */ + Config withValue(String path, ConfigValue value); } diff --git a/config/src/main/java/com/typesafe/config/ConfigObject.java b/config/src/main/java/com/typesafe/config/ConfigObject.java index 285bf04e..54f6bd81 100644 --- a/config/src/main/java/com/typesafe/config/ConfigObject.java +++ b/config/src/main/java/com/typesafe/config/ConfigObject.java @@ -110,4 +110,18 @@ public interface ConfigObject extends ConfigValue, Map { * @return a copy of the object minus the specified key */ ConfigObject withoutKey(String key); + + /** + * Returns a {@code ConfigObject} based on this one, but with the given key + * set to the given value. Does not modify this instance (since it's + * immutable). If the key already has a value, that value is replaced. To + * remove a value, use withoutKey(). + * + * @param key + * key to add + * @param value + * value at the new key + * @return the new instance with the new map entry + */ + ConfigObject withValue(String key, ConfigValue value); } diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index ba44075f..d4dfc6ad 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -42,12 +42,17 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi @Override abstract public AbstractConfigObject withoutKey(String key); + @Override + abstract public AbstractConfigObject withValue(String key, ConfigValue value); + abstract protected AbstractConfigObject withOnlyPathOrNull(Path path); abstract AbstractConfigObject withOnlyPath(Path path); abstract AbstractConfigObject withoutPath(Path path); + abstract AbstractConfigObject withValue(Path path, ConfigValue value); + /** * This looks up the key with no transformation or type conversion of any * kind, and returns null if the key is not present. The object must be 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 834ae269..8750b967 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -9,7 +9,6 @@ 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; @@ -340,10 +339,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue { return atKey(SimpleConfigOrigin.newSimple("atKey(" + key + ")"), key); } - @Override - public Config atPath(String pathExpression) { - SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("atPath(" + pathExpression + ")"); - Path path = Path.newPath(pathExpression); + SimpleConfig atPath(ConfigOrigin origin, Path path) { Path parent = path.parent(); SimpleConfig result = atKey(origin, path.last()); while (parent != null) { @@ -353,4 +349,10 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue { } return result; } + + @Override + public SimpleConfig atPath(String pathExpression) { + SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("atPath(" + pathExpression + ")"); + return atPath(origin, Path.newPath(pathExpression)); + } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java index 1eb87c50..7befaea9 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -140,6 +140,16 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm throw notResolved(); } + @Override + public ConfigDelayedMergeObject withValue(String key, ConfigValue value) { + throw notResolved(); + } + + @Override + ConfigDelayedMergeObject withValue(Path path, ConfigValue value) { + throw notResolved(); + } + @Override public Collection unmergedValues() { return stack; 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 a30eedd6..d368e144 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,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return new SimpleConfig(root().withoutPath(path)); } + @Override + public SimpleConfig withValue(String pathExpression, ConfigValue v) { + Path path = Path.newPath(pathExpression); + return new SimpleConfig(root().withValue(path, v)); + } + SimpleConfig atKey(ConfigOrigin origin, String key) { return root().atKey(origin, key); } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 1eee5de5..56f2aa96 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -130,6 +130,45 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa } } + @Override + public SimpleConfigObject withValue(String key, ConfigValue v) { + if (v == null) + throw new ConfigException.BugOrBroken( + "Trying to store null ConfigValue in a ConfigObject"); + + Map newMap; + if (value.isEmpty()) { + newMap = Collections.singletonMap(key, (AbstractConfigValue) v); + } else { + newMap = new HashMap(value); + newMap.put(key, (AbstractConfigValue) v); + } + + return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values()), + ignoresFallbacks); + } + + @Override + SimpleConfigObject withValue(Path path, ConfigValue v) { + String key = path.first(); + Path next = path.remainder(); + + if (next == null) { + return withValue(key, v); + } else { + AbstractConfigValue child = value.get(key); + if (child != null && child instanceof AbstractConfigObject) { + // if we have an object, add to it + return withValue(key, ((AbstractConfigObject) child).withValue(next, v)); + } else { + // as soon as we have a non-object, replace it entirely + SimpleConfig subtree = ((AbstractConfigValue) v).atPath( + SimpleConfigOrigin.newSimple("withValue(" + next.render() + ")"), next); + return withValue(key, subtree.root()); + } + } + } + @Override protected AbstractConfigValue attemptPeekWithPartialResolve(String key) { return value.get(key); 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 04b981cb..45a88a65 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -15,6 +15,7 @@ import com.typesafe.config.ConfigException import com.typesafe.config.ConfigValueType import com.typesafe.config.ConfigOrigin import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.ConfigFactory class ConfigValueTest extends TestUtils { @@ -788,6 +789,72 @@ class ConfigValueTest extends TestUtils { assertTrue(config.origin.description.contains("atKey")) } + @Test + def withValueDepth1FromEmpty() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = ConfigFactory.empty.withValue("a", v) + assertEquals(parseConfig("a=42"), config) + assertTrue(config.getValue("a") eq v) + } + + @Test + def withValueDepth2FromEmpty() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = ConfigFactory.empty.withValue("a.b", v) + assertEquals(parseConfig("a.b=42"), config) + assertTrue(config.getValue("a.b") eq v) + } + + @Test + def withValueDepth3FromEmpty() { + val v = ConfigValueFactory.fromAnyRef(42) + val config = ConfigFactory.empty.withValue("a.b.c", v) + assertEquals(parseConfig("a.b.c=42"), config) + assertTrue(config.getValue("a.b.c") eq v) + } + + @Test + def withValueDepth1OverwritesExisting() { + val v = ConfigValueFactory.fromAnyRef(47) + val old = v.atPath("a") + val config = old.withValue("a", ConfigValueFactory.fromAnyRef(42)) + assertEquals(parseConfig("a=42"), config) + assertEquals(42, config.getInt("a")) + } + + @Test + def withValueDepth2OverwritesExisting() { + val v = ConfigValueFactory.fromAnyRef(47) + val old = v.atPath("a.b") + val config = old.withValue("a.b", ConfigValueFactory.fromAnyRef(42)) + assertEquals(parseConfig("a.b=42"), config) + assertEquals(42, config.getInt("a.b")) + } + + @Test + def withValueInsideExistingObject() { + val v = ConfigValueFactory.fromAnyRef(47) + val old = v.atPath("a.c") + val config = old.withValue("a.b", ConfigValueFactory.fromAnyRef(42)) + assertEquals(parseConfig("a.b=42,a.c=47"), config) + assertEquals(42, config.getInt("a.b")) + assertEquals(47, config.getInt("a.c")) + } + + @Test + def withValueBuildComplexConfig() { + val v1 = ConfigValueFactory.fromAnyRef(1) + val v2 = ConfigValueFactory.fromAnyRef(2) + val v3 = ConfigValueFactory.fromAnyRef(3) + val v4 = ConfigValueFactory.fromAnyRef(4) + val config = ConfigFactory.empty + .withValue("a", v1) + .withValue("b.c", v2) + .withValue("b.d", v3) + .withValue("x.y.z", v4) + assertEquals(parseConfig("a=1,b.c=2,b.d=3,x.y.z=4"), config) + } + @Test def configOriginsInSerialization() { import scala.collection.JavaConverters._