Refactor withFallback() implementations to share more logic among subtypes

Also add some tests and fix some correctness issues.

If we fall back to a value that has ignoresFallbacks()=true, then we need
to "catch" its ignoresFallbacks.

Don't create new RootConfig objects if the underlying has not changed.

Get rid of ConfigImpl.merge() utility, no longer useful.
This commit is contained in:
Havoc Pennington 2011-11-18 16:12:46 -05:00
parent 7df8e342f6
commit 8b6f4c6156
10 changed files with 268 additions and 150 deletions

View File

@ -106,21 +106,31 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
boolean ignoresFallbacks); boolean ignoresFallbacks);
@Override @Override
public AbstractConfigObject withFallback(ConfigMergeable mergeable) { protected AbstractConfigObject newCopy(boolean ignoresFallbacks) {
ConfigValue other = mergeable.toValue(); return newCopy(resolveStatus(), ignoresFallbacks);
}
@Override
protected final AbstractConfigObject mergedWithTheUnmergeable(Unmergeable fallback) {
if (ignoresFallbacks())
throw new ConfigException.BugOrBroken("should not be reached");
if (ignoresFallbacks()) {
return this;
} else if (other instanceof Unmergeable) {
List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>(); List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
stack.add(this); if (this instanceof Unmergeable) {
stack.addAll(((Unmergeable) other).unmergedValues()); stack.addAll(((Unmergeable) this).unmergedValues());
return new ConfigDelayedMergeObject(mergeOrigins(stack), stack);
} else if (other instanceof AbstractConfigObject) {
AbstractConfigObject fallback = (AbstractConfigObject) other;
if (fallback.isEmpty()) {
return this; // nothing to do
} else { } else {
stack.add(this);
}
stack.addAll(fallback.unmergedValues());
return new ConfigDelayedMergeObject(mergeOrigins(stack), stack,
((AbstractConfigValue) fallback).ignoresFallbacks());
}
@Override
protected AbstractConfigObject mergedWithObject(AbstractConfigObject fallback) {
if (ignoresFallbacks())
throw new ConfigException.BugOrBroken("should not be reached");
boolean allResolved = true; boolean allResolved = true;
Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>(); Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
Set<String> allKeys = new HashSet<String>(); Set<String> allKeys = new HashSet<String>();
@ -140,16 +150,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (kept.resolveStatus() == ResolveStatus.UNRESOLVED) if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
allResolved = false; allResolved = false;
} }
return new SimpleConfigObject(mergeOrigins(this, fallback), return new SimpleConfigObject(mergeOrigins(this, fallback), merged,
merged, ResolveStatus.fromBoolean(allResolved), ResolveStatus.fromBoolean(allResolved), fallback.ignoresFallbacks());
ignoresFallbacks());
}
} else {
// falling back to a non-object doesn't merge anything, and also
// prohibits merging any objects that we fall back to later.
// so we have to switch to ignoresFallbacks mode.
return newCopy(resolveStatus(), true /* ignoresFallbacks */);
} }
@Override
public AbstractConfigObject withFallback(ConfigMergeable mergeable) {
return (AbstractConfigObject) super.withFallback(mergeable);
} }
static ConfigOrigin mergeOrigins( static ConfigOrigin mergeOrigins(
@ -169,7 +176,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (desc.startsWith(prefix)) if (desc.startsWith(prefix))
desc = desc.substring(prefix.length()); desc = desc.substring(prefix.length());
if (v instanceof ConfigObject && ((ConfigObject) v).isEmpty()) { if (v instanceof AbstractConfigObject
&& ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED
&& ((ConfigObject) v).isEmpty()) {
// don't include empty files or the .empty() // don't include empty files or the .empty()
// config in the description, since they are // config in the description, since they are
// likely to be "implementation details" // likely to be "implementation details"

View File

@ -3,6 +3,7 @@
*/ */
package com.typesafe.config.impl; package com.typesafe.config.impl;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigResolveOptions;
@ -75,6 +76,10 @@ abstract class AbstractConfigValue implements ConfigValue {
return this; return this;
} }
protected AbstractConfigValue newCopy(boolean ignoresFallbacks) {
return this;
}
// this is virtualized rather than a field because only some subclasses // this is virtualized rather than a field because only some subclasses
// really need to store the boolean, and they may be able to pack it // really need to store the boolean, and they may be able to pack it
// with another boolean to save space. // with another boolean to save space.
@ -82,9 +87,50 @@ abstract class AbstractConfigValue implements ConfigValue {
return true; return true;
} }
private ConfigException badMergeException() {
if (ignoresFallbacks())
throw new ConfigException.BugOrBroken(
"method should not have been called with ignoresFallbacks=true"
+ getClass().getSimpleName());
else
throw new ConfigException.BugOrBroken("should override this in "
+ getClass().getSimpleName());
}
protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
throw badMergeException();
}
protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
throw badMergeException();
}
@Override @Override
public AbstractConfigValue withFallback(ConfigMergeable other) { public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
if (ignoresFallbacks()) {
return this; return this;
} else {
ConfigValue other = mergeable.toValue();
if (other instanceof Unmergeable) {
return mergedWithTheUnmergeable((Unmergeable) other);
} else if (other instanceof AbstractConfigObject) {
AbstractConfigObject fallback = (AbstractConfigObject) other;
if (fallback.resolveStatus() == ResolveStatus.RESOLVED && fallback.isEmpty()) {
if (fallback.ignoresFallbacks())
return newCopy(true /* ignoresFallbacks */);
else
return this;
} else {
return mergedWithObject((AbstractConfigObject) other);
}
} else {
// falling back to a non-object doesn't merge anything, and also
// prohibits merging any objects that we fall back to later.
// so we have to switch to ignoresFallbacks mode.
return newCopy(true /* ignoresFallbacks */);
}
}
} }
protected boolean canEqual(Object other) { protected boolean canEqual(Object other) {

View File

@ -8,10 +8,8 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType; import com.typesafe.config.ConfigValueType;
/** /**
@ -29,8 +27,8 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
final private List<AbstractConfigValue> stack; final private List<AbstractConfigValue> stack;
final private boolean ignoresFallbacks; final private boolean ignoresFallbacks;
private ConfigDelayedMerge(ConfigOrigin origin, ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack,
List<AbstractConfigValue> stack, boolean ignoresFallbacks) { boolean ignoresFallbacks) {
super(origin); super(origin);
this.stack = stack; this.stack = stack;
this.ignoresFallbacks = ignoresFallbacks; this.ignoresFallbacks = ignoresFallbacks;
@ -38,6 +36,11 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"creating empty delayed merge value"); "creating empty delayed merge value");
for (AbstractConfigValue v : stack) {
if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject)
throw new ConfigException.BugOrBroken(
"placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack");
}
} }
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack) { ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack) {
@ -67,18 +70,19 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
List<AbstractConfigValue> stack, SubstitutionResolver resolver, List<AbstractConfigValue> stack, SubstitutionResolver resolver,
int depth, ConfigResolveOptions options) { int depth, ConfigResolveOptions options) {
// to resolve substitutions, we need to recursively resolve // to resolve substitutions, we need to recursively resolve
// the stack of stuff to merge, and then merge the stack. // the stack of stuff to merge, and merge the stack so
List<AbstractConfigValue> toMerge = new ArrayList<AbstractConfigValue>(); // we won't be a delayed merge anymore.
AbstractConfigValue merged = null;
for (AbstractConfigValue v : stack) { for (AbstractConfigValue v : stack) {
AbstractConfigValue resolved = resolver.resolve(v, depth, options); AbstractConfigValue resolved = resolver.resolve(v, depth, options);
toMerge.add(resolved); if (merged == null)
merged = resolved;
else
merged = merged.withFallback(resolved);
} }
// we shouldn't have a delayed merge object with an empty stack, so return merged;
// it should be safe to ignore the toMerge.isEmpty case.
return ConfigImpl.merge(AbstractConfigValue.class, toMerge.get(0),
toMerge.subList(1, toMerge.size()));
} }
@Override @Override
@ -101,29 +105,31 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
} }
@Override @Override
public AbstractConfigValue withFallback(ConfigMergeable mergeable) { protected final ConfigDelayedMerge mergedWithTheUnmergeable(Unmergeable fallback) {
ConfigValue other = mergeable.toValue(); if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("should not be reached");
if (ignoresFallbacks) {
return this;
} else if (other instanceof AbstractConfigObject
|| other instanceof Unmergeable) {
// if we turn out to be an object, and the fallback also does, // if we turn out to be an object, and the fallback also does,
// then a merge may be required; delay until we resolve. // then a merge may be required; delay until we resolve.
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>(); List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.addAll(stack); newStack.addAll(stack);
if (other instanceof Unmergeable) newStack.addAll(fallback.unmergedValues());
newStack.addAll(((Unmergeable) other).unmergedValues()); return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
else ((AbstractConfigValue) fallback).ignoresFallbacks());
newStack.add((AbstractConfigValue) other);
return new ConfigDelayedMerge(
AbstractConfigObject.mergeOrigins(newStack), newStack,
ignoresFallbacks);
} 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.
return new ConfigDelayedMerge(origin(), stack, true /* ignoresFallbacks */);
} }
@Override
protected final ConfigDelayedMerge mergedWithObject(AbstractConfigObject fallback) {
if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("should not be reached");
// if we turn out to be an object, and the fallback also does,
// then a merge may be required; delay until we resolve.
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.addAll(stack);
newStack.add(fallback);
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
fallback.ignoresFallbacks());
} }
@Override @Override

View File

@ -28,8 +28,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
this(origin, stack, false /* ignoresFallbacks */); this(origin, stack, false /* ignoresFallbacks */);
} }
private ConfigDelayedMergeObject(ConfigOrigin origin, ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack,
List<AbstractConfigValue> stack, boolean ignoresFallbacks) { boolean ignoresFallbacks) {
super(origin); super(origin);
this.stack = stack; this.stack = stack;
this.ignoresFallbacks = ignoresFallbacks; this.ignoresFallbacks = ignoresFallbacks;
@ -40,10 +40,16 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
if (!(stack.get(0) instanceof AbstractConfigObject)) if (!(stack.get(0) instanceof AbstractConfigObject))
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"created a delayed merge object not guaranteed to be an object"); "created a delayed merge object not guaranteed to be an object");
for (AbstractConfigValue v : stack) {
if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject)
throw new ConfigException.BugOrBroken(
"placed nested DelayedMerge in a ConfigDelayedMergeObject, should have consolidated stack");
}
} }
@Override @Override
public ConfigDelayedMergeObject newCopy(ResolveStatus status, protected ConfigDelayedMergeObject newCopy(ResolveStatus status,
boolean ignoresFallbacks) { boolean ignoresFallbacks) {
if (status != resolveStatus()) if (status != resolveStatus())
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
@ -86,30 +92,22 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
} }
@Override @Override
public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) { protected ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) {
ConfigValue other = mergeable.toValue(); if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("should not be reached");
if (ignoresFallbacks) { // since we are an object, and the fallback is, we'll need to
return this; // merge the fallback once we resolve.
} else if (other instanceof AbstractConfigObject
|| other instanceof Unmergeable) {
// since we are an object, and the fallback could be,
// then a merge may be required; delay until we resolve.
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>(); List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.addAll(stack); newStack.addAll(stack);
if (other instanceof Unmergeable) newStack.add(fallback);
newStack.addAll(((Unmergeable) other).unmergedValues()); return new ConfigDelayedMergeObject(AbstractConfigObject.mergeOrigins(newStack), newStack,
else fallback.ignoresFallbacks());
newStack.add((AbstractConfigValue) other);
return new ConfigDelayedMergeObject(
AbstractConfigObject.mergeOrigins(newStack), newStack,
ignoresFallbacks);
} else {
// if the other is not an object, there won't be anything
// to merge with but we do need to ignore any fallbacks
// that occur after it.
return newCopy(resolveStatus(), true /* ignoresFallbacks */);
} }
@Override
public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
return (ConfigDelayedMergeObject) super.withFallback(mergeable);
} }
@Override @Override

View File

@ -5,7 +5,6 @@ package com.typesafe.config.impl;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -16,7 +15,6 @@ import com.typesafe.config.Config;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigIncludeContext; import com.typesafe.config.ConfigIncludeContext;
import com.typesafe.config.ConfigIncluder; import com.typesafe.config.ConfigIncluder;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseOptions;
@ -28,21 +26,6 @@ import com.typesafe.config.ConfigValue;
/** This is public but is only supposed to be used by the "config" package */ /** This is public but is only supposed to be used by the "config" package */
public class ConfigImpl { public class ConfigImpl {
static <T extends ConfigMergeable> T merge(Class<T> klass, T first,
ConfigMergeable... others) {
List<ConfigMergeable> stack = Arrays.asList(others);
return merge(klass, first, stack);
}
static <T extends ConfigMergeable> T merge(Class<T> klass, T first,
List<? extends ConfigMergeable> stack) {
ConfigMergeable merged = first;
for (ConfigMergeable fallback : stack) {
merged = merged.withFallback(fallback);
}
return klass.cast(merged);
}
private interface NameSource { private interface NameSource {
ConfigParseable nameToParseable(String name); ConfigParseable nameToParseable(String name);
} }

View File

@ -9,7 +9,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValue;
@ -57,28 +56,41 @@ final class ConfigSubstitution extends AbstractConfigValue implements
} }
@Override @Override
public AbstractConfigValue withFallback(ConfigMergeable mergeable) { protected ConfigSubstitution newCopy(boolean ignoresFallbacks) {
ConfigValue other = mergeable.toValue(); return new ConfigSubstitution(origin(), pieces, prefixLength, ignoresFallbacks);
}
@Override
protected boolean ignoresFallbacks() {
return ignoresFallbacks;
}
@Override
protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("should not be reached");
if (ignoresFallbacks) {
return this;
} else if (other instanceof AbstractConfigObject
|| other instanceof Unmergeable) {
// if we turn out to be an object, and the fallback also does, // if we turn out to be an object, and the fallback also does,
// then a merge may be required; delay until we resolve. // then a merge may be required; delay until we resolve.
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>(); List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.add(this); newStack.add(this);
if (other instanceof Unmergeable) newStack.addAll(fallback.unmergedValues());
newStack.addAll(((Unmergeable) other).unmergedValues()); return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
else ((AbstractConfigValue) fallback).ignoresFallbacks());
newStack.add((AbstractConfigValue) other);
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.
return new ConfigSubstitution(origin(), pieces, prefixLength, true /* ignoresFallbacks */);
} }
@Override
protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("should not be reached");
// if we turn out to be an object, and the fallback also does,
// then a merge may be required; delay until we resolve.
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.add(this);
newStack.add(fallback);
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
fallback.ignoresFallbacks());
} }
@Override @Override

View File

@ -35,14 +35,14 @@ final class RootConfig extends SimpleConfig implements ConfigRoot {
// if the object is already resolved then we should end up returning // if the object is already resolved then we should end up returning
// "this" here, since asRoot() should return this if the path // "this" here, since asRoot() should return this if the path
// is unchanged. // is unchanged.
SimpleConfig resolved = resolvedObject(options).toConfig(); AbstractConfigObject resolved = resolvedObject(options);
return resolved.asRoot(rootPath); return newRootIfObjectChanged(this, resolved);
} }
@Override @Override
public RootConfig withFallback(ConfigMergeable value) { public RootConfig withFallback(ConfigMergeable value) {
// this can return "this" if the withFallback does nothing // this can return "this" if the withFallback does nothing
return super.withFallback(value).asRoot(rootPath); return newRootIfObjectChanged(this, super.withFallback(value).toObject());
} }
Path rootPathObject() { Path rootPathObject() {

View File

@ -59,6 +59,13 @@ class SimpleConfig implements Config {
return new RootConfig(underlying, 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) { protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(object, AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
object, options); object, options);

View File

@ -45,10 +45,8 @@ final class SimpleConfigObject extends AbstractConfigObject {
} }
@Override @Override
public SimpleConfigObject newCopy(ResolveStatus newStatus, protected SimpleConfigObject newCopy(ResolveStatus newStatus, boolean newIgnoresFallbacks) {
boolean ignoresFallbacks) { return new SimpleConfigObject(origin(), value, newStatus, newIgnoresFallbacks);
return new SimpleConfigObject(origin(), value, newStatus,
ignoresFallbacks);
} }
@Override @Override

View File

@ -15,6 +15,7 @@ import com.typesafe.config.ConfigResolveOptions
import java.io.File import java.io.File
import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigMergeable
class ConfigTest extends TestUtils { class ConfigTest extends TestUtils {
@ -83,7 +84,10 @@ class ConfigTest extends TestUtils {
val trees = Seq(m(allObjects: _*)) ++ makeTrees(allObjects) val trees = Seq(m(allObjects: _*)) ++ makeTrees(allObjects)
for (tree <- trees) { for (tree <- trees) {
// if this fails, we were not associative. // if this fails, we were not associative.
assertEquals(trees(0), tree) if (!trees(0).equals(tree))
throw new AssertionError("Merge was not associative, " +
"verify that it should not be, then don't use associativeMerge " +
"for this one.\none: " + trees(0) + "\ntwo: " + tree)
} }
for (tree <- trees) { for (tree <- trees) {
@ -445,6 +449,61 @@ class ConfigTest extends TestUtils {
} }
} }
private def ignoresFallbacks(m: ConfigMergeable) = {
m match {
case v: AbstractConfigValue =>
v.ignoresFallbacks()
case c: SimpleConfig =>
c.toObject.ignoresFallbacks()
}
}
private def testIgnoredMergesDoNothing(nonEmpty: ConfigMergeable) {
// falling back to a primitive once should switch us to "ignoreFallbacks" mode
// and then twice should "return this". Falling back to an empty object should
// return this unless the empty object was ignoreFallbacks and then we should
// "catch" its ignoreFallbacks.
// some of what this tests is just optimization, not API contract (withFallback
// can return a new object anytime it likes) but want to be sure we do the
// optimizations.
val empty = SimpleConfigObject.empty(null)
val primitive = intValue(42)
val emptyIgnoringFallbacks = empty.withFallback(primitive)
val nonEmptyIgnoringFallbacks = nonEmpty.withFallback(primitive)
assertEquals(false, empty.ignoresFallbacks())
assertEquals(true, primitive.ignoresFallbacks())
assertEquals(true, emptyIgnoringFallbacks.ignoresFallbacks())
assertEquals(false, ignoresFallbacks(nonEmpty))
assertEquals(true, ignoresFallbacks(nonEmptyIgnoringFallbacks))
assertTrue(nonEmpty ne nonEmptyIgnoringFallbacks)
assertTrue(empty ne emptyIgnoringFallbacks)
// falling back from primitive just returns this
assertTrue(primitive eq primitive.withFallback(empty))
assertTrue(primitive eq primitive.withFallback(nonEmpty))
assertTrue(primitive eq primitive.withFallback(nonEmptyIgnoringFallbacks))
// falling back again from an ignoreFallbacks should be a no-op, return this
assertTrue(nonEmptyIgnoringFallbacks eq nonEmptyIgnoringFallbacks.withFallback(empty))
assertTrue(nonEmptyIgnoringFallbacks eq nonEmptyIgnoringFallbacks.withFallback(primitive))
assertTrue(emptyIgnoringFallbacks eq emptyIgnoringFallbacks.withFallback(empty))
assertTrue(emptyIgnoringFallbacks eq emptyIgnoringFallbacks.withFallback(primitive))
}
@Test
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 @Test
def test01Getting() { def test01Getting() {
val conf = ConfigFactory.load("test01") val conf = ConfigFactory.load("test01")