mirror of
https://github.com/lightbend/config.git
synced 2025-01-15 23:01:05 +08:00
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:
parent
7df8e342f6
commit
8b6f4c6156
@ -106,50 +106,57 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
boolean ignoresFallbacks);
|
||||
|
||||
@Override
|
||||
public AbstractConfigObject withFallback(ConfigMergeable mergeable) {
|
||||
ConfigValue other = mergeable.toValue();
|
||||
protected AbstractConfigObject newCopy(boolean ignoresFallbacks) {
|
||||
return newCopy(resolveStatus(), ignoresFallbacks);
|
||||
}
|
||||
|
||||
if (ignoresFallbacks()) {
|
||||
return this;
|
||||
} else if (other instanceof Unmergeable) {
|
||||
List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
|
||||
stack.add(this);
|
||||
stack.addAll(((Unmergeable) other).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 {
|
||||
boolean allResolved = true;
|
||||
Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
|
||||
Set<String> allKeys = new HashSet<String>();
|
||||
allKeys.addAll(this.keySet());
|
||||
allKeys.addAll(fallback.keySet());
|
||||
for (String key : allKeys) {
|
||||
AbstractConfigValue first = this.peek(key);
|
||||
AbstractConfigValue second = fallback.peek(key);
|
||||
AbstractConfigValue kept;
|
||||
if (first == null)
|
||||
kept = second;
|
||||
else if (second == null)
|
||||
kept = first;
|
||||
else
|
||||
kept = first.withFallback(second);
|
||||
merged.put(key, kept);
|
||||
if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
|
||||
allResolved = false;
|
||||
}
|
||||
return new SimpleConfigObject(mergeOrigins(this, fallback),
|
||||
merged, ResolveStatus.fromBoolean(allResolved),
|
||||
ignoresFallbacks());
|
||||
}
|
||||
@Override
|
||||
protected final AbstractConfigObject mergedWithTheUnmergeable(Unmergeable fallback) {
|
||||
if (ignoresFallbacks())
|
||||
throw new ConfigException.BugOrBroken("should not be reached");
|
||||
|
||||
List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
|
||||
if (this instanceof Unmergeable) {
|
||||
stack.addAll(((Unmergeable) this).unmergedValues());
|
||||
} 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 */);
|
||||
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;
|
||||
Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
|
||||
Set<String> allKeys = new HashSet<String>();
|
||||
allKeys.addAll(this.keySet());
|
||||
allKeys.addAll(fallback.keySet());
|
||||
for (String key : allKeys) {
|
||||
AbstractConfigValue first = this.peek(key);
|
||||
AbstractConfigValue second = fallback.peek(key);
|
||||
AbstractConfigValue kept;
|
||||
if (first == null)
|
||||
kept = second;
|
||||
else if (second == null)
|
||||
kept = first;
|
||||
else
|
||||
kept = first.withFallback(second);
|
||||
merged.put(key, kept);
|
||||
if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
|
||||
allResolved = false;
|
||||
}
|
||||
return new SimpleConfigObject(mergeOrigins(this, fallback), merged,
|
||||
ResolveStatus.fromBoolean(allResolved), fallback.ignoresFallbacks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractConfigObject withFallback(ConfigMergeable mergeable) {
|
||||
return (AbstractConfigObject) super.withFallback(mergeable);
|
||||
}
|
||||
|
||||
static ConfigOrigin mergeOrigins(
|
||||
@ -169,7 +176,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
if (desc.startsWith(prefix))
|
||||
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()
|
||||
// config in the description, since they are
|
||||
// likely to be "implementation details"
|
||||
|
@ -3,6 +3,7 @@
|
||||
*/
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigMergeable;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigResolveOptions;
|
||||
@ -75,6 +76,10 @@ abstract class AbstractConfigValue implements ConfigValue {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected AbstractConfigValue newCopy(boolean ignoresFallbacks) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 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
|
||||
// with another boolean to save space.
|
||||
@ -82,9 +87,50 @@ abstract class AbstractConfigValue implements ConfigValue {
|
||||
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
|
||||
public AbstractConfigValue withFallback(ConfigMergeable other) {
|
||||
return this;
|
||||
public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
|
||||
if (ignoresFallbacks()) {
|
||||
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) {
|
||||
|
@ -8,10 +8,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigMergeable;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigResolveOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import com.typesafe.config.ConfigValueType;
|
||||
|
||||
/**
|
||||
@ -29,8 +27,8 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
||||
final private List<AbstractConfigValue> stack;
|
||||
final private boolean ignoresFallbacks;
|
||||
|
||||
private ConfigDelayedMerge(ConfigOrigin origin,
|
||||
List<AbstractConfigValue> stack, boolean ignoresFallbacks) {
|
||||
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack,
|
||||
boolean ignoresFallbacks) {
|
||||
super(origin);
|
||||
this.stack = stack;
|
||||
this.ignoresFallbacks = ignoresFallbacks;
|
||||
@ -38,6 +36,11 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"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) {
|
||||
@ -67,18 +70,19 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
||||
List<AbstractConfigValue> stack, SubstitutionResolver resolver,
|
||||
int depth, ConfigResolveOptions options) {
|
||||
// to resolve substitutions, we need to recursively resolve
|
||||
// the stack of stuff to merge, and then merge the stack.
|
||||
List<AbstractConfigValue> toMerge = new ArrayList<AbstractConfigValue>();
|
||||
// the stack of stuff to merge, and merge the stack so
|
||||
// we won't be a delayed merge anymore.
|
||||
|
||||
AbstractConfigValue merged = null;
|
||||
for (AbstractConfigValue v : stack) {
|
||||
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
|
||||
// it should be safe to ignore the toMerge.isEmpty case.
|
||||
return ConfigImpl.merge(AbstractConfigValue.class, toMerge.get(0),
|
||||
toMerge.subList(1, toMerge.size()));
|
||||
return merged;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,29 +105,31 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
|
||||
ConfigValue other = mergeable.toValue();
|
||||
protected final ConfigDelayedMerge 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,
|
||||
// then a merge may be required; delay until we resolve.
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.addAll(stack);
|
||||
if (other instanceof Unmergeable)
|
||||
newStack.addAll(((Unmergeable) other).unmergedValues());
|
||||
else
|
||||
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 */);
|
||||
}
|
||||
// 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.addAll(fallback.unmergedValues());
|
||||
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
((AbstractConfigValue) fallback).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
|
||||
|
@ -28,8 +28,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
|
||||
this(origin, stack, false /* ignoresFallbacks */);
|
||||
}
|
||||
|
||||
private ConfigDelayedMergeObject(ConfigOrigin origin,
|
||||
List<AbstractConfigValue> stack, boolean ignoresFallbacks) {
|
||||
ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack,
|
||||
boolean ignoresFallbacks) {
|
||||
super(origin);
|
||||
this.stack = stack;
|
||||
this.ignoresFallbacks = ignoresFallbacks;
|
||||
@ -40,10 +40,16 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
|
||||
if (!(stack.get(0) instanceof AbstractConfigObject))
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"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
|
||||
public ConfigDelayedMergeObject newCopy(ResolveStatus status,
|
||||
protected ConfigDelayedMergeObject newCopy(ResolveStatus status,
|
||||
boolean ignoresFallbacks) {
|
||||
if (status != resolveStatus())
|
||||
throw new ConfigException.BugOrBroken(
|
||||
@ -86,30 +92,22 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
|
||||
ConfigValue other = mergeable.toValue();
|
||||
protected ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) {
|
||||
if (ignoresFallbacks)
|
||||
throw new ConfigException.BugOrBroken("should not be reached");
|
||||
|
||||
if (ignoresFallbacks) {
|
||||
return this;
|
||||
} 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>();
|
||||
newStack.addAll(stack);
|
||||
if (other instanceof Unmergeable)
|
||||
newStack.addAll(((Unmergeable) other).unmergedValues());
|
||||
else
|
||||
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 */);
|
||||
}
|
||||
// since we are an object, and the fallback is, we'll need to
|
||||
// merge the fallback once we resolve.
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.addAll(stack);
|
||||
newStack.add(fallback);
|
||||
return new ConfigDelayedMergeObject(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
fallback.ignoresFallbacks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
|
||||
return (ConfigDelayedMergeObject) super.withFallback(mergeable);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,7 +5,6 @@ package com.typesafe.config.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@ -16,7 +15,6 @@ import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigIncludeContext;
|
||||
import com.typesafe.config.ConfigIncluder;
|
||||
import com.typesafe.config.ConfigMergeable;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
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 */
|
||||
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 {
|
||||
ConfigParseable nameToParseable(String name);
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigMergeable;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigResolveOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
@ -57,28 +56,41 @@ final class ConfigSubstitution extends AbstractConfigValue implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
|
||||
ConfigValue other = mergeable.toValue();
|
||||
protected ConfigSubstitution newCopy(boolean ignoresFallbacks) {
|
||||
return new ConfigSubstitution(origin(), pieces, prefixLength, ignoresFallbacks);
|
||||
}
|
||||
|
||||
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,
|
||||
// then a merge may be required; delay until we resolve.
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.add(this);
|
||||
if (other instanceof Unmergeable)
|
||||
newStack.addAll(((Unmergeable) other).unmergedValues());
|
||||
else
|
||||
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 boolean ignoresFallbacks() {
|
||||
return ignoresFallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable 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.addAll(fallback.unmergedValues());
|
||||
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
((AbstractConfigValue) fallback).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
|
||||
|
@ -35,14 +35,14 @@ final class RootConfig extends SimpleConfig implements ConfigRoot {
|
||||
// if the object is already resolved then we should end up returning
|
||||
// "this" here, since asRoot() should return this if the path
|
||||
// is unchanged.
|
||||
SimpleConfig resolved = resolvedObject(options).toConfig();
|
||||
return resolved.asRoot(rootPath);
|
||||
AbstractConfigObject resolved = resolvedObject(options);
|
||||
return newRootIfObjectChanged(this, resolved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootConfig withFallback(ConfigMergeable value) {
|
||||
// this can return "this" if the withFallback does nothing
|
||||
return super.withFallback(value).asRoot(rootPath);
|
||||
return newRootIfObjectChanged(this, super.withFallback(value).toObject());
|
||||
}
|
||||
|
||||
Path rootPathObject() {
|
||||
|
@ -59,6 +59,13 @@ class SimpleConfig implements Config {
|
||||
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) {
|
||||
AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
|
||||
object, options);
|
||||
|
@ -45,10 +45,8 @@ final class SimpleConfigObject extends AbstractConfigObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleConfigObject newCopy(ResolveStatus newStatus,
|
||||
boolean ignoresFallbacks) {
|
||||
return new SimpleConfigObject(origin(), value, newStatus,
|
||||
ignoresFallbacks);
|
||||
protected SimpleConfigObject newCopy(ResolveStatus newStatus, boolean newIgnoresFallbacks) {
|
||||
return new SimpleConfigObject(origin(), value, newStatus, newIgnoresFallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,7 @@ import com.typesafe.config.ConfigResolveOptions
|
||||
import java.io.File
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigMergeable
|
||||
|
||||
class ConfigTest extends TestUtils {
|
||||
|
||||
@ -83,7 +84,10 @@ class ConfigTest extends TestUtils {
|
||||
val trees = Seq(m(allObjects: _*)) ++ makeTrees(allObjects)
|
||||
for (tree <- trees) {
|
||||
// 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) {
|
||||
@ -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
|
||||
def test01Getting() {
|
||||
val conf = ConfigFactory.load("test01")
|
||||
|
Loading…
Reference in New Issue
Block a user