clean up merge() so it doesn't need origin or transformer

origin can be computed by combining origins of merged values.

transformer should be set prior to merge; probably it should
be an error to merge with mismatched transformers.
This commit is contained in:
Havoc Pennington 2011-11-11 17:18:26 -05:00
parent 8f0e6bd5f0
commit 8c00a97243
11 changed files with 99 additions and 63 deletions

View File

@ -1,7 +1,8 @@
package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -25,6 +26,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
ConfigTransformer transformer) {
super(origin);
this.transformer = transformer;
if (transformer == null)
throw new ConfigException.BugOrBroken("null transformer");
}
/**
@ -166,7 +169,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
stack.add(this);
stack.addAll(((Unresolved) other).unmergedValues());
return new ConfigDelayedMergeObject(origin(), transformer, stack);
return new ConfigDelayedMergeObject(mergeOrigins(stack),
transformer, stack);
} else if (other instanceof AbstractConfigObject) {
AbstractConfigObject fallback = (AbstractConfigObject) other;
if (fallback.isEmpty()) {
@ -186,7 +190,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
else
merged.put(key, first.withFallback(second));
}
return new SimpleConfigObject(origin(), transformer, merged);
return new SimpleConfigObject(mergeOrigins(this, fallback),
transformer, merged);
}
} else {
// falling back to a non-object has no effect, we just override
@ -195,25 +200,45 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
}
}
static ConfigOrigin mergeOrigins(
Collection<? extends AbstractConfigValue> stack) {
if (stack.isEmpty())
throw new ConfigException.BugOrBroken(
"can't merge origins on empty list");
StringBuilder sb = new StringBuilder();
String prefix = "merge of ";
sb.append(prefix);
for (AbstractConfigValue v : stack) {
String desc = v.origin().description();
if (desc.startsWith(prefix))
desc = desc.substring(prefix.length());
sb.append(desc);
sb.append(",");
}
sb.setLength(sb.length() - 1); // chop comma
return new SimpleConfigOrigin(sb.toString());
}
static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) {
return mergeOrigins(Arrays.asList(stack));
}
/**
* Stack should be from overrides to fallbacks (earlier items win). Objects
* have their keys combined into a new object, while other kinds of value
* are just first-one-wins.
*/
static AbstractConfigObject merge(ConfigOrigin origin,
List<AbstractConfigObject> stack,
ConfigTransformer transformer) {
static AbstractConfigObject merge(List<AbstractConfigObject> stack) {
if (stack.isEmpty()) {
return new SimpleConfigObject(origin, transformer,
Collections.<String, AbstractConfigValue> emptyMap());
return SimpleConfigObject.empty();
} else if (stack.size() == 1) {
return stack.get(0).transformed(transformer);
return stack.get(0);
} else {
AbstractConfigObject merged = stack.get(0);
for (int i = 1; i < stack.size(); ++i) {
merged = merged.withFallback(stack.get(i));
}
return merged.transformed(transformer);
return merged;
}
}

View File

@ -82,8 +82,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
}
}
return AbstractConfigObject.merge(toMerge.get(0).origin(), toMerge,
toMerge.get(0).transformer);
return AbstractConfigObject.merge(toMerge);
}
@Override
@ -98,7 +97,8 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
newStack.addAll(((Unresolved) other).unmergedValues());
else
newStack.add((AbstractConfigValue) other);
return new ConfigDelayedMerge(origin(), newStack);
return new ConfigDelayedMerge(
AbstractConfigObject.mergeOrigins(newStack), newStack);
} else {
// if the other is not an object, there won't be anything
// to merge with, so we are it even if we are an object.

View File

@ -18,7 +18,8 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements
final private List<AbstractConfigValue> stack;
ConfigDelayedMergeObject(ConfigOrigin origin,
ConfigTransformer transformer, List<AbstractConfigValue> stack) {
ConfigTransformer transformer,
List<AbstractConfigValue> stack) {
super(origin, transformer);
this.stack = stack;
if (stack.isEmpty())
@ -55,7 +56,9 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements
newStack.addAll(((Unresolved) other).unmergedValues());
else
newStack.add((AbstractConfigValue) other);
return new ConfigDelayedMergeObject(origin(), transformer, newStack);
return new ConfigDelayedMergeObject(
AbstractConfigObject.mergeOrigins(newStack), transformer,
newStack);
} else {
// if the other is not an object, there won't be anything
// to merge with.

View File

@ -12,6 +12,8 @@ import com.typesafe.config.ConfigObject;
/** This is public but is only supposed to be used by the "config" package */
public class ConfigImpl {
public static ConfigObject loadConfig(ConfigConfig configConfig) {
ConfigTransformer transformer = withExtraTransformer(null);
AbstractConfigObject system = null;
try {
system = systemPropertiesConfig()
@ -23,19 +25,16 @@ public class ConfigImpl {
// higher-priority configs are first
if (system != null)
stack.add(system);
stack.add(system.transformed(transformer));
// this is a conceptual placeholder for a customizable
// object that the app might be able to pass in.
IncludeHandler includer = defaultIncluder();
stack.add(includer.include(configConfig.rootPath()));
stack.add(includer.include(configConfig.rootPath()).transformed(
transformer));
ConfigTransformer transformer = withExtraTransformer(null);
AbstractConfigObject merged = AbstractConfigObject
.merge(new SimpleConfigOrigin("config for "
+ configConfig.rootPath()), stack, transformer);
AbstractConfigObject merged = AbstractConfigObject.merge(stack);
AbstractConfigValue resolved = SubstitutionResolver.resolve(merged,
merged);

View File

@ -53,7 +53,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements
newStack.addAll(((Unresolved) other).unmergedValues());
else
newStack.add((AbstractConfigValue) other);
return new ConfigDelayedMerge(origin(), newStack);
return new ConfigDelayedMerge(
AbstractConfigObject.mergeOrigins(newStack), newStack);
} else {
// if the other is not an object, there won't be anything
// to merge with, so we are it even if we are an object.

View File

@ -30,9 +30,7 @@ final class Loader {
addResource(name + ".properties", includer, stack);
}
AbstractConfigObject merged = AbstractConfigObject.merge(
new SimpleConfigOrigin("config for " + name), stack,
ConfigImpl.defaultConfigTransformer());
AbstractConfigObject merged = AbstractConfigObject.merge(stack);
return merged;
}
@ -158,7 +156,8 @@ final class Loader {
// its contract, but since we know nobody has a ref to this
// SimpleConfigObject yet we can get away with it.
AbstractConfigObject o = new SimpleConfigObject(
new SimpleConfigOrigin(originPrefix + " " + path), null,
new SimpleConfigOrigin(originPrefix + " " + path),
ConfigImpl.defaultConfigTransformer(),
scopes.get(path));
String basename = lastElement(path);
parent.put(basename, o);
@ -173,6 +172,6 @@ final class Loader {
// return root config object
return new SimpleConfigObject(new SimpleConfigOrigin(originPrefix),
null, root);
ConfigImpl.defaultConfigTransformer(), root);
}
}

View File

@ -319,7 +319,8 @@ final class Parser {
+ t);
}
}
return new SimpleConfigObject(objectOrigin, null, values);
return new SimpleConfigObject(objectOrigin,
ConfigImpl.defaultConfigTransformer(), values);
}
private SimpleConfigList parseArray() {

View File

@ -141,4 +141,10 @@ final class SimpleConfigObject extends AbstractConfigObject {
public Collection<ConfigValue> values() {
return new HashSet<ConfigValue>(value.values());
}
final static SimpleConfigObject empty() {
return new SimpleConfigObject(new SimpleConfigOrigin("empty config"),
ConfigImpl.defaultConfigTransformer(),
Collections.<String, AbstractConfigValue> emptyMap());
}
}

View File

@ -13,7 +13,7 @@ import com.typesafe.config.ConfigConfig
class ConfigTest extends TestUtils {
def merge(toMerge: AbstractConfigObject*) = {
AbstractConfigObject.merge(fakeOrigin(), toMerge.toList.asJava, null)
AbstractConfigObject.merge(toMerge.toList.asJava)
}
// In many cases, we expect merging to be associative. It is
@ -69,7 +69,7 @@ class ConfigTest extends TestUtils {
def mergeTrivial() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ "b" : 2 }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
assertEquals(1, merged.getInt("a"))
assertEquals(2, merged.getInt("b"))
@ -78,7 +78,7 @@ class ConfigTest extends TestUtils {
@Test
def mergeEmpty() {
val merged = AbstractConfigObject.merge(fakeOrigin(), List[AbstractConfigObject]().asJava, null)
val merged = merge()
assertEquals(0, merged.keySet().size)
}
@ -86,7 +86,7 @@ class ConfigTest extends TestUtils {
@Test
def mergeOne() {
val obj1 = parseObject("""{ "a" : 1 }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1).asJava, null)
val merged = merge(obj1)
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
@ -96,12 +96,12 @@ class ConfigTest extends TestUtils {
def mergeOverride() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ "a" : 2 }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
val merged2 = AbstractConfigObject.merge(fakeOrigin(), List(obj2, obj1).asJava, null)
val merged2 = merge(obj2, obj1)
assertEquals(2, merged2.getInt("a"))
assertEquals(1, merged2.keySet().size)
@ -113,7 +113,7 @@ class ConfigTest extends TestUtils {
val obj2 = parseObject("""{ "b" : 2 }""")
val obj3 = parseObject("""{ "c" : 3 }""")
val obj4 = parseObject("""{ "d" : 4 }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2, obj3, obj4).asJava, null)
val merged = merge(obj1, obj2, obj3, obj4)
assertEquals(1, merged.getInt("a"))
assertEquals(2, merged.getInt("b"))
@ -128,12 +128,12 @@ class ConfigTest extends TestUtils {
val obj2 = parseObject("""{ "a" : 2 }""")
val obj3 = parseObject("""{ "a" : 3 }""")
val obj4 = parseObject("""{ "a" : 4 }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2, obj3, obj4).asJava, null)
val merged = merge(obj1, obj2, obj3, obj4)
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
val merged2 = AbstractConfigObject.merge(fakeOrigin(), List(obj4, obj3, obj2, obj1).asJava, null)
val merged2 = merge(obj4, obj3, obj2, obj1)
assertEquals(4, merged2.getInt("a"))
assertEquals(1, merged2.keySet().size)
@ -143,7 +143,7 @@ class ConfigTest extends TestUtils {
def mergeNested() {
val obj1 = parseObject("""{ "root" : { "a" : 1, "z" : 101 } }""")
val obj2 = parseObject("""{ "root" : { "b" : 2, "z" : 102 } }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
assertEquals(1, merged.getInt("root.a"))
assertEquals(2, merged.getInt("root.b"))
@ -156,12 +156,12 @@ class ConfigTest extends TestUtils {
def mergeWithEmpty() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
val merged2 = AbstractConfigObject.merge(fakeOrigin(), List(obj2, obj1).asJava, null)
val merged2 = merge(obj2, obj1)
assertEquals(1, merged2.getInt("a"))
assertEquals(1, merged2.keySet().size)
@ -171,12 +171,12 @@ class ConfigTest extends TestUtils {
def mergeOverrideObjectAndPrimitive() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ "a" : { "b" : 42 } }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
val merged2 = AbstractConfigObject.merge(fakeOrigin(), List(obj2, obj1).asJava, null)
val merged2 = merge(obj2, obj1)
assertEquals(42, merged2.getObject("a").getInt("b"))
assertEquals(42, merged2.getInt("a.b"))
@ -218,7 +218,7 @@ class ConfigTest extends TestUtils {
val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : ${a} }""")
val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
case x: ConfigObject => x
}
@ -234,7 +234,7 @@ class ConfigTest extends TestUtils {
val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : { "z" : 42 } }""")
val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""")
val merged = AbstractConfigObject.merge(fakeOrigin(), List(obj1, obj2).asJava, null)
val merged = merge(obj1, obj2)
val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
case x: ConfigObject => x
}
@ -243,7 +243,7 @@ class ConfigTest extends TestUtils {
assertEquals(2, resolved.getInt("c.y"))
assertEquals(42, resolved.getInt("c.z"))
val merged2 = AbstractConfigObject.merge(fakeOrigin(), List(obj2, obj1).asJava, null)
val merged2 = merge(obj2, obj1)
val resolved2 = SubstitutionResolver.resolveWithoutFallbacks(merged2, merged2) match {
case x: ConfigObject => x
}
@ -274,7 +274,7 @@ class ConfigTest extends TestUtils {
assertTrue(e.getMessage().contains("cycle"))
val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : 57 } } } """)
val merged = AbstractConfigObject.merge(fakeOrigin(), List(fixUpCycle, cycleObject).asJava, null)
val merged = merge(fixUpCycle, cycleObject)
val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), merged)
assertEquals(intValue(57), v);
}
@ -290,7 +290,7 @@ class ConfigTest extends TestUtils {
assertTrue(e.getMessage().contains("cycle"))
val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : { "q" : "u" } } } } """)
val merged = AbstractConfigObject.merge(fakeOrigin(), List(fixUpCycle, cycleObject).asJava, null)
val merged = merge(fixUpCycle, cycleObject)
val e2 = intercept[ConfigException.BadValue] {
val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), merged)
}

View File

@ -60,9 +60,9 @@ class ConfigValueTest extends TestUtils {
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val sameAsAMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val bMap = configMap("a" -> 3, "b" -> 4, "c" -> 5)
val a = new SimpleConfigObject(fakeOrigin(), null, aMap)
val sameAsA = new SimpleConfigObject(fakeOrigin(), null, sameAsAMap)
val b = new SimpleConfigObject(fakeOrigin(), null, bMap)
val a = new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), aMap)
val sameAsA = new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), sameAsAMap)
val b = new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), bMap)
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
@ -108,12 +108,12 @@ class ConfigValueTest extends TestUtils {
@Test
def configDelayedMergeObjectEquality() {
val empty = new SimpleConfigObject(fakeOrigin(), null, Collections.emptyMap[String, AbstractConfigValue]())
val empty = SimpleConfigObject.empty()
val s1 = subst("foo")
val s2 = subst("bar")
val a = new ConfigDelayedMergeObject(fakeOrigin(), null, List[AbstractConfigValue](empty, s1, s2).asJava)
val sameAsA = new ConfigDelayedMergeObject(fakeOrigin(), null, List[AbstractConfigValue](empty, s1, s2).asJava)
val b = new ConfigDelayedMergeObject(fakeOrigin(), null, List[AbstractConfigValue](empty, s2, s1).asJava)
val a = new ConfigDelayedMergeObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), List[AbstractConfigValue](empty, s1, s2).asJava)
val sameAsA = new ConfigDelayedMergeObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), List[AbstractConfigValue](empty, s1, s2).asJava)
val b = new ConfigDelayedMergeObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), List[AbstractConfigValue](empty, s2, s1).asJava)
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
@ -130,14 +130,14 @@ class ConfigValueTest extends TestUtils {
stringValue("hi").toString()
nullValue().toString()
boolValue(true).toString()
val emptyObj = new SimpleConfigObject(fakeOrigin(), null, Collections.emptyMap[String, AbstractConfigValue]())
val emptyObj = SimpleConfigObject.empty()
emptyObj.toString()
(new SimpleConfigList(fakeOrigin(), Collections.emptyList[AbstractConfigValue]())).toString()
subst("a").toString()
substInString("b").toString()
val dm = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](subst("a"), subst("b")).asJava)
dm.toString()
val dmo = new ConfigDelayedMergeObject(fakeOrigin(), null, List[AbstractConfigValue](emptyObj, subst("a"), subst("b")).asJava)
val dmo = new ConfigDelayedMergeObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), List[AbstractConfigValue](emptyObj, subst("a"), subst("b")).asJava)
dmo.toString()
}
@ -149,13 +149,15 @@ class ConfigValueTest extends TestUtils {
@Test
def configObjectUnwraps() {
val m = new SimpleConfigObject(fakeOrigin(), null, configMap("a" -> 1, "b" -> 2, "c" -> 3))
val m = new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(),
configMap("a" -> 1, "b" -> 2, "c" -> 3))
assertEquals(Map("a" -> 1, "b" -> 2, "c" -> 3), m.unwrapped().asScala)
}
@Test
def configObjectImplementsMap() {
val m: ConfigObject = new SimpleConfigObject(fakeOrigin(), null, configMap("a" -> 1, "b" -> 2, "c" -> 3))
val m: ConfigObject = new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(),
configMap("a" -> 1, "b" -> 2, "c" -> 3))
assertEquals(intValue(1), m.get("a"))
assertEquals(intValue(2), m.get("b"))
@ -275,8 +277,8 @@ class ConfigValueTest extends TestUtils {
unresolved { dm.unwrapped() }
// ConfigDelayedMergeObject
val emptyObj = new SimpleConfigObject(fakeOrigin(), null, Collections.emptyMap[String, AbstractConfigValue]())
val dmo = new ConfigDelayedMergeObject(fakeOrigin(), null, List[AbstractConfigValue](emptyObj, subst("a"), subst("b")).asJava)
val emptyObj = new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), Collections.emptyMap[String, AbstractConfigValue]())
val dmo = new ConfigDelayedMergeObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), List[AbstractConfigValue](emptyObj, subst("a"), subst("b")).asJava)
assertEquals(ConfigValueType.OBJECT, dmo.valueType())
unresolved { dmo.unwrapped() }
unresolved { dmo.containsKey(null) }

View File

@ -49,7 +49,7 @@ class JsonTest extends TestUtils {
case lift.JObject(fields) =>
val m = new HashMap[String, AbstractConfigValue]()
fields.foreach({ field => m.put(field.name, fromLift(field.value)) })
new SimpleConfigObject(fakeOrigin(), null, m)
new SimpleConfigObject(fakeOrigin(), ConfigImpl.defaultConfigTransformer(), m)
case lift.JArray(values) =>
new SimpleConfigList(fakeOrigin(), values.map(fromLift(_)).asJava)
case lift.JField(name, value) =>
@ -126,7 +126,7 @@ class JsonTest extends TestUtils {
// be sure we do the same thing as Lift when we build our JSON "DOM"
for (valid <- whitespaceVariations(validJson)) {
val liftAST = if (valid.liftBehaviorUnexpected) {
new SimpleConfigObject(fakeOrigin(), null, Collections.emptyMap[String, AbstractConfigValue]())
SimpleConfigObject.empty()
} else {
addOffendingJsonToException("lift", valid.test) {
fromJsonWithLiftParser(valid.test)