mirror of
https://github.com/lightbend/config.git
synced 2025-03-29 05:20:42 +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,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"
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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() {
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user