add withValue method to Config and ConfigObject

This makes it easier to build up a config in code.
This commit is contained in:
Havoc Pennington 2012-07-04 11:56:50 -04:00
parent d15ade876a
commit bd820702e6
9 changed files with 166 additions and 5 deletions

View File

@ -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

View File

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

View File

@ -110,4 +110,18 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
* @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);
}

View File

@ -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

View File

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

View File

@ -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<AbstractConfigValue> unmergedValues() {
return stack;

View File

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

View File

@ -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<String, AbstractConfigValue> newMap;
if (value.isEmpty()) {
newMap = Collections.singletonMap(key, (AbstractConfigValue) v);
} else {
newMap = new HashMap<String, AbstractConfigValue>(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);

View File

@ -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._