mirror of
https://github.com/lightbend/config.git
synced 2025-02-24 02:00:46 +08:00
Fix semantics of references from _inside_ a field value to that field
vs. the value itself being a reference to the field. So this looks "backward": a : ${a} vs. this does not: a : { b : ${a} }
This commit is contained in:
parent
06ed4d24e1
commit
b696dbee38
@ -156,19 +156,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi
|
||||
}
|
||||
|
||||
@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 {
|
||||
stack.add(this);
|
||||
}
|
||||
stack.addAll(fallback.unmergedValues());
|
||||
return new ConfigDelayedMergeObject(mergeOrigins(stack), stack,
|
||||
((AbstractConfigValue) fallback).ignoresFallbacks());
|
||||
protected AbstractConfigObject constructDelayedMerge(ConfigOrigin origin,
|
||||
List<AbstractConfigValue> stack) {
|
||||
return new ConfigDelayedMergeObject(origin, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4,6 +4,10 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigMergeable;
|
||||
@ -124,32 +128,91 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue, Seria
|
||||
// really need to store the boolean, and they may be able to pack it
|
||||
// with another boolean to save space.
|
||||
protected boolean ignoresFallbacks() {
|
||||
return true;
|
||||
// if we are not resolved, then somewhere in this value there's
|
||||
// a substitution that may need to look at the fallbacks.
|
||||
return resolveStatus() == ResolveStatus.RESOLVED;
|
||||
}
|
||||
|
||||
private ConfigException badMergeException() {
|
||||
// the withFallback() implementation is supposed to avoid calling
|
||||
// mergedWith* if we're ignoring fallbacks.
|
||||
protected final void requireNotIgnoringFallbacks() {
|
||||
if (ignoresFallbacks())
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"method should not have been called with ignoresFallbacks=true"
|
||||
"method should not have been called with ignoresFallbacks=true "
|
||||
+ getClass().getSimpleName());
|
||||
else
|
||||
throw new ConfigException.BugOrBroken("should override this in "
|
||||
+ getClass().getSimpleName());
|
||||
}
|
||||
|
||||
protected AbstractConfigValue constructDelayedMerge(ConfigOrigin origin,
|
||||
List<AbstractConfigValue> stack) {
|
||||
return new ConfigDelayedMerge(origin, stack);
|
||||
}
|
||||
|
||||
protected final AbstractConfigValue mergedWithTheUnmergeable(
|
||||
Collection<AbstractConfigValue> stack, Unmergeable fallback) {
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
// 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 constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack);
|
||||
}
|
||||
|
||||
private final AbstractConfigValue delayMerge(Collection<AbstractConfigValue> stack,
|
||||
AbstractConfigValue fallback) {
|
||||
// if we turn out to be an object, and the fallback also does,
|
||||
// then a merge may be required.
|
||||
// if we contain a substitution, resolving it may need to look
|
||||
// back to the fallback.
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.addAll(stack);
|
||||
newStack.add(fallback);
|
||||
return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack);
|
||||
}
|
||||
|
||||
protected final AbstractConfigValue mergedWithObject(Collection<AbstractConfigValue> stack,
|
||||
AbstractConfigObject fallback) {
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
if (this instanceof AbstractConfigObject)
|
||||
throw new ConfigException.BugOrBroken("Objects must reimplement mergedWithObject");
|
||||
|
||||
return mergedWithNonObject(stack, fallback);
|
||||
}
|
||||
|
||||
protected final AbstractConfigValue mergedWithNonObject(Collection<AbstractConfigValue> stack,
|
||||
AbstractConfigValue fallback) {
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
if (resolveStatus() == ResolveStatus.RESOLVED) {
|
||||
// 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 */, origin);
|
||||
} else {
|
||||
// if unresolved, we may have to look back to fallbacks as part of
|
||||
// the resolution process, so always delay
|
||||
return delayMerge(stack, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
|
||||
throw badMergeException();
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
return mergedWithTheUnmergeable(Collections.singletonList(this), fallback);
|
||||
}
|
||||
|
||||
protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
|
||||
throw badMergeException();
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
return mergedWithObject(Collections.singletonList(this), fallback);
|
||||
}
|
||||
|
||||
protected AbstractConfigValue mergedWithNonObject(AbstractConfigValue fallback) {
|
||||
// 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 */, origin);
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
return mergedWithNonObject(Collections.singletonList(this), fallback);
|
||||
}
|
||||
|
||||
public AbstractConfigValue withOrigin(ConfigOrigin origin) {
|
||||
@ -159,6 +222,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue, Seria
|
||||
return newCopy(ignoresFallbacks(), origin);
|
||||
}
|
||||
|
||||
// this is only overridden to change the return type
|
||||
@Override
|
||||
public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
|
||||
if (ignoresFallbacks()) {
|
||||
@ -169,15 +233,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue, Seria
|
||||
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 */, origin);
|
||||
else
|
||||
return this;
|
||||
} else {
|
||||
return mergedWithObject((AbstractConfigObject) other);
|
||||
}
|
||||
return mergedWithObject((AbstractConfigObject) other);
|
||||
} else {
|
||||
return mergedWithNonObject((AbstractConfigValue) other);
|
||||
}
|
||||
|
@ -60,43 +60,6 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
|
||||
// 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());
|
||||
}
|
||||
|
||||
protected AbstractConfigValue mergedLater(AbstractConfigValue fallback) {
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.add(this);
|
||||
newStack.add(fallback);
|
||||
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
fallback.ignoresFallbacks());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
|
||||
// if we turn out to be an object, and the fallback also does,
|
||||
// then a merge may be required; delay until we resolve.
|
||||
return mergedLater(fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithNonObject(AbstractConfigValue fallback) {
|
||||
// We may need the fallback if we contain a self-referential
|
||||
// ConfigReference.
|
||||
//
|
||||
// we can't easily detect the self-referential case since the cycle
|
||||
// may involve more than one step, so we have to wait and
|
||||
// merge later when resolving.
|
||||
return mergedLater(fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigConcatenation> unmergedValues() {
|
||||
return Collections.singleton(this);
|
||||
|
@ -28,13 +28,15 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
|
||||
|
||||
// earlier items in the stack win
|
||||
final private List<AbstractConfigValue> stack;
|
||||
final private boolean ignoresFallbacks;
|
||||
// this is just here for serialization compat; whether we ignore is purely
|
||||
// a function of the bottom of the merge stack
|
||||
@SuppressWarnings("unused")
|
||||
@Deprecated
|
||||
final private boolean ignoresFallbacks = false;
|
||||
|
||||
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack,
|
||||
boolean ignoresFallbacks) {
|
||||
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack) {
|
||||
super(origin);
|
||||
this.stack = stack;
|
||||
this.ignoresFallbacks = ignoresFallbacks;
|
||||
if (stack.isEmpty())
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"creating empty delayed merge value");
|
||||
@ -46,10 +48,6 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
|
||||
}
|
||||
}
|
||||
|
||||
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack) {
|
||||
this(origin, stack, false /* ignoresFallbacks */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigValueType valueType() {
|
||||
throw new ConfigException.NotResolved(
|
||||
@ -80,10 +78,9 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
|
||||
AbstractConfigValue merged = null;
|
||||
for (AbstractConfigValue v : stack) {
|
||||
boolean replaced = false;
|
||||
// checking for RESOLVED already is just an optimization
|
||||
// to avoid creating the replacer when it can't possibly
|
||||
// be needed.
|
||||
if (v.resolveStatus() != ResolveStatus.RESOLVED) {
|
||||
// we only replace if we have a substitution, or
|
||||
// value-concatenation containing one
|
||||
if (v instanceof Unmergeable) {
|
||||
// If, while resolving 'v' we come back to the same
|
||||
// merge stack, we only want to look _below_ 'v'
|
||||
// in the stack. So we arrange to replace the
|
||||
@ -158,50 +155,38 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
|
||||
for (AbstractConfigValue o : stack) {
|
||||
newStack.add(o.relativized(prefix));
|
||||
}
|
||||
return new ConfigDelayedMerge(origin(), newStack, ignoresFallbacks);
|
||||
return new ConfigDelayedMerge(origin(), newStack);
|
||||
}
|
||||
|
||||
// static utility shared with ConfigDelayedMergeObject
|
||||
static boolean stackIgnoresFallbacks(List<AbstractConfigValue> stack) {
|
||||
AbstractConfigValue last = stack.get(stack.size() - 1);
|
||||
return last.ignoresFallbacks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean ignoresFallbacks() {
|
||||
return ignoresFallbacks;
|
||||
return stackIgnoresFallbacks(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue newCopy(boolean newIgnoresFallbacks, ConfigOrigin newOrigin) {
|
||||
return new ConfigDelayedMerge(newOrigin, stack, newIgnoresFallbacks);
|
||||
return new ConfigDelayedMerge(newOrigin, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ConfigDelayedMerge 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.addAll(stack);
|
||||
newStack.addAll(fallback.unmergedValues());
|
||||
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
((AbstractConfigValue) fallback).ignoresFallbacks());
|
||||
return (ConfigDelayedMerge) mergedWithTheUnmergeable(stack, fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ConfigDelayedMerge mergedWithObject(AbstractConfigObject fallback) {
|
||||
return mergedWithNonObject(fallback);
|
||||
return (ConfigDelayedMerge) mergedWithObject(stack, fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigDelayedMerge mergedWithNonObject(AbstractConfigValue 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());
|
||||
return (ConfigDelayedMerge) mergedWithNonObject(stack, fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -300,6 +285,6 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
// switch to LinkedList
|
||||
return new ConfigDelayedMerge(origin(),
|
||||
new java.util.LinkedList<AbstractConfigValue>(stack), ignoresFallbacks);
|
||||
new java.util.LinkedList<AbstractConfigValue>(stack));
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigList;
|
||||
import com.typesafe.config.ConfigMergeable;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
@ -23,18 +24,16 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
final private List<AbstractConfigValue> stack;
|
||||
final private boolean ignoresFallbacks;
|
||||
|
||||
ConfigDelayedMergeObject(ConfigOrigin origin,
|
||||
List<AbstractConfigValue> stack) {
|
||||
this(origin, stack, false /* ignoresFallbacks */);
|
||||
}
|
||||
// this is just here for serialization compat; whether we ignore is purely
|
||||
// a function of the bottom of the merge stack
|
||||
@SuppressWarnings("unused")
|
||||
@Deprecated
|
||||
final private boolean ignoresFallbacks = false;
|
||||
|
||||
ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack,
|
||||
boolean ignoresFallbacks) {
|
||||
ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack) {
|
||||
super(origin);
|
||||
this.stack = stack;
|
||||
this.ignoresFallbacks = ignoresFallbacks;
|
||||
|
||||
if (stack.isEmpty())
|
||||
throw new ConfigException.BugOrBroken(
|
||||
@ -56,7 +55,7 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
|
||||
if (status != resolveStatus())
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"attempt to create resolved ConfigDelayedMergeObject");
|
||||
return new ConfigDelayedMergeObject(origin, stack, ignoresFallbacks);
|
||||
return new ConfigDelayedMergeObject(origin, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,30 +92,31 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
|
||||
for (AbstractConfigValue o : stack) {
|
||||
newStack.add(o.relativized(prefix));
|
||||
}
|
||||
return new ConfigDelayedMergeObject(origin(), newStack,
|
||||
ignoresFallbacks);
|
||||
return new ConfigDelayedMergeObject(origin(), newStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean ignoresFallbacks() {
|
||||
return ignoresFallbacks;
|
||||
return ConfigDelayedMerge.stackIgnoresFallbacks(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) {
|
||||
protected final ConfigDelayedMergeObject mergedWithTheUnmergeable(Unmergeable fallback) {
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
return (ConfigDelayedMergeObject) mergedWithTheUnmergeable(stack, fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) {
|
||||
return mergedWithNonObject(fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigDelayedMergeObject mergedWithNonObject(AbstractConfigValue fallback) {
|
||||
if (ignoresFallbacks)
|
||||
throw new ConfigException.BugOrBroken("should not be reached");
|
||||
protected final ConfigDelayedMergeObject mergedWithNonObject(AbstractConfigValue fallback) {
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.addAll(stack);
|
||||
newStack.add(fallback);
|
||||
return new ConfigDelayedMergeObject(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
fallback.ignoresFallbacks());
|
||||
return (ConfigDelayedMergeObject) mergedWithNonObject(stack, fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -249,10 +249,6 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
|
||||
// to touch the exact key that isn't resolved, so this is in that
|
||||
// spirit.
|
||||
|
||||
// this function should never return null; if we know a value doesn't
|
||||
// exist, then there would be no reason for the merge to be delayed
|
||||
// (i.e. as long as some stuff is unmerged, the value may be non-null).
|
||||
|
||||
// we'll be able to return a key if we have a value that ignores
|
||||
// fallbacks, prior to any unmergeable values.
|
||||
for (AbstractConfigValue layer : stack) {
|
||||
@ -289,17 +285,33 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
|
||||
throw new ConfigException.NotResolved("Key '" + key + "' is not available at '"
|
||||
+ origin().description() + "' because value at '"
|
||||
+ layer.origin().description()
|
||||
+ "' has not been resolved and may turn out to contain '" + key + "'."
|
||||
+ "' has not been resolved and may turn out to contain or hide '" + key
|
||||
+ "'."
|
||||
+ " Be sure to Config#resolve() before using a config object.");
|
||||
} else if (layer.resolveStatus() == ResolveStatus.UNRESOLVED) {
|
||||
// if the layer is not an object, and not a substitution or
|
||||
// merge,
|
||||
// then it's something that's unresolved because it _contains_
|
||||
// an unresolved object... i.e. it's an array
|
||||
if (!(layer instanceof ConfigList))
|
||||
throw new ConfigException.BugOrBroken("Expecting a list here, not " + layer);
|
||||
// all later objects will be hidden so we can say we won't find
|
||||
// the key
|
||||
return null;
|
||||
} else {
|
||||
// non-object, but not unresolved, like an integer or something.
|
||||
// non-object, but resolved, like an integer or something.
|
||||
// has no children so the one we're after won't be in it.
|
||||
// this should always be overridden by an object though so
|
||||
// ideally we never build a stack that would have this in it.
|
||||
continue;
|
||||
// we would only have this in the stack in case something
|
||||
// else "looks back" to it due to a cycle.
|
||||
// anyway at this point we know we can't find the key anymore.
|
||||
if (!layer.ignoresFallbacks()) {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"resolved non-object should ignore fallbacks");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// If we get here, then we never found an unmergeable which means
|
||||
// If we get here, then we never found anything unresolved which means
|
||||
// the ConfigDelayedMergeObject should not have existed. some
|
||||
// invariant was violated.
|
||||
throw new ConfigException.BugOrBroken(
|
||||
@ -314,6 +326,6 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
// switch to LinkedList
|
||||
return new ConfigDelayedMergeObject(origin(),
|
||||
new java.util.LinkedList<AbstractConfigValue>(stack), ignoresFallbacks);
|
||||
new java.util.LinkedList<AbstractConfigValue>(stack));
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
@ -59,45 +57,6 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
|
||||
// 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());
|
||||
}
|
||||
|
||||
protected AbstractConfigValue mergedLater(AbstractConfigValue fallback) {
|
||||
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
|
||||
newStack.add(this);
|
||||
newStack.add(fallback);
|
||||
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
|
||||
fallback.ignoresFallbacks());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
|
||||
// if we turn out to be an object, and the fallback also does,
|
||||
// then a merge may be required; delay until we resolve.
|
||||
return mergedLater(fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue mergedWithNonObject(AbstractConfigValue fallback) {
|
||||
// We may need the fallback for two reasons:
|
||||
// 1. if an optional substitution ends up getting deleted
|
||||
// because it is not defined
|
||||
// 2. if the substitution is self-referential
|
||||
//
|
||||
// we can't easily detect the self-referential case since the cycle
|
||||
// may involve more than one step, so we have to wait and
|
||||
// merge later when resolving.
|
||||
return mergedLater(fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigReference> unmergedValues() {
|
||||
return Collections.singleton(this);
|
||||
|
@ -93,7 +93,6 @@ final class ResolveContext {
|
||||
}
|
||||
|
||||
AbstractConfigValue resolve(AbstractConfigValue original) throws NotPossibleToResolve {
|
||||
|
||||
// a fully-resolved (no restrictToChild) object can satisfy a
|
||||
// request for a restricted object, so always check that first.
|
||||
final MemoKey fullKey = new MemoKey(original, null);
|
||||
|
@ -159,8 +159,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
|
||||
|
||||
@Override
|
||||
protected SimpleConfigObject mergedWithObject(AbstractConfigObject abstractFallback) {
|
||||
if (ignoresFallbacks())
|
||||
throw new ConfigException.BugOrBroken("should not be reached");
|
||||
requireNotIgnoringFallbacks();
|
||||
|
||||
if (!(abstractFallback instanceof SimpleConfigObject)) {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
|
@ -1039,6 +1039,20 @@ class ConfigSubstitutionTest extends TestUtils {
|
||||
assertEquals("optional self reference disappears", 0, resolved.root.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceAlongPath() {
|
||||
val obj = parseObject("""a.b=1, a.b=${a.b}""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(1, resolved.getInt("a.b"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceAlongLongerPath() {
|
||||
val obj = parseObject("""a.b.c=1, a.b.c=${a.b.c}""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(1, resolved.getInt("a.b.c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceIndirect() {
|
||||
val obj = parseObject("""a=1, b=${a}, a=${b}""")
|
||||
@ -1077,6 +1091,13 @@ class ConfigSubstitutionTest extends TestUtils {
|
||||
assertEquals(5, resolved.getInt("a.b"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceObjectAlongPath() {
|
||||
val obj = parseObject("""a.b={c=5}, a.b=${a.b}""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(5, resolved.getInt("a.b.c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceInConcat() {
|
||||
val obj = parseObject("""a=1, a=${a}foo""")
|
||||
@ -1174,6 +1195,15 @@ class ConfigSubstitutionTest extends TestUtils {
|
||||
assertEquals(5, resolved.getInt("a.c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceAlongAPathInsideObject() {
|
||||
// if the ${a.b} is _inside_ a field value instead of
|
||||
// _being_ the field value, it does not look backward.
|
||||
val obj = parseObject("""a={b={c=5}}, a={ x : ${a.b} }, a={b=2}""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(2, resolved.getInt("a.x"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substInChildFieldNotASelfReference1() {
|
||||
// here, ${bar.foo} is not a self reference because
|
||||
@ -1220,6 +1250,37 @@ class ConfigSubstitutionTest extends TestUtils {
|
||||
assertEquals(42, resolved.getInt("bar.foo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substInChildFieldNotASelfReference4() {
|
||||
// checking that having bar set to non-object earlier
|
||||
// doesn't break the behavior.
|
||||
val obj = parseObject("""
|
||||
bar : 101
|
||||
bar : { foo : 42,
|
||||
baz : ${bar.foo}
|
||||
}
|
||||
""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(42, resolved.getInt("bar.baz"))
|
||||
assertEquals(42, resolved.getInt("bar.foo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substInChildFieldNotASelfReference5() {
|
||||
// checking that having bar set to unresolved array earlier
|
||||
// doesn't break the behavior.
|
||||
val obj = parseObject("""
|
||||
x : 0
|
||||
bar : [ ${x}, 1, 2, 3 ]
|
||||
bar : { foo : 42,
|
||||
baz : ${bar.foo}
|
||||
}
|
||||
""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(42, resolved.getInt("bar.baz"))
|
||||
assertEquals(42, resolved.getInt("bar.foo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def mutuallyReferringNotASelfReference() {
|
||||
val obj = parseObject("""
|
||||
@ -1248,4 +1309,42 @@ class ConfigSubstitutionTest extends TestUtils {
|
||||
val resolved = resolve(obj)
|
||||
assertEquals("1xyz", resolved.getString("a"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceInArray() {
|
||||
// never "look back" from "inside" an array
|
||||
val obj = parseObject("""a=1,a=[${a}, 2]""")
|
||||
val e = intercept[ConfigException.UnresolvedSubstitution] {
|
||||
resolve(obj)
|
||||
}
|
||||
assertTrue("wrong exception: " + e.getMessage,
|
||||
e.getMessage.contains("cycle") && e.getMessage.contains("${a}"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def substSelfReferenceInObject() {
|
||||
// never "look back" from "inside" an object
|
||||
val obj = parseObject("""a=1,a={ x : ${a} }""")
|
||||
val e = intercept[ConfigException.UnresolvedSubstitution] {
|
||||
resolve(obj)
|
||||
}
|
||||
assertTrue("wrong exception: " + e.getMessage,
|
||||
e.getMessage.contains("cycle") && e.getMessage.contains("${a}"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def selfReferentialObjectNotAffectedByOverriding() {
|
||||
// this is testing that we can still refer to another
|
||||
// field in the same object, even though we are overriding
|
||||
// an earlier object.
|
||||
val obj = parseObject("""a={ x : 42, y : ${a.x} }""")
|
||||
val resolved = resolve(obj)
|
||||
assertEquals(parseObject("{ x : 42, y : 42 }"), resolved.getConfig("a").root)
|
||||
|
||||
// this is expected because if adding "a=1" here affects the outcome,
|
||||
// it would be flat-out bizarre.
|
||||
val obj2 = parseObject("""a=1, a={ x : 42, y : ${a.x} }""")
|
||||
val resolved2 = resolve(obj2)
|
||||
assertEquals(parseObject("{ x : 42, y : 42 }"), resolved2.getConfig("a").root)
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ class ConfigTest extends TestUtils {
|
||||
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 this one. two results were: \none: " + trees(0) + "\ntwo: " +
|
||||
tree + "\noriginal list: " + allObjects)
|
||||
}
|
||||
|
||||
for (tree <- trees) {
|
||||
@ -503,6 +504,20 @@ class ConfigTest extends TestUtils {
|
||||
testIgnoredMergesDoNothing(conf)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testNoMergeAcrossArray() {
|
||||
val conf = parseConfig("a: {b:1}, a: [2,3], a:{c:4}")
|
||||
assertFalse("a.b found in: " + conf, conf.hasPath("a.b"))
|
||||
assertTrue("a.c not found in: " + conf, conf.hasPath("a.c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testNoMergeAcrossUnresolvedArray() {
|
||||
val conf = parseConfig("a: {b:1}, a: [2,${x}], a:{c:4}, x: 42")
|
||||
assertFalse("a.b found in: " + conf, conf.hasPath("a.b"))
|
||||
assertTrue("a.c not found in: " + conf, conf.hasPath("a.c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def integerRangeChecks() {
|
||||
val conf = parseConfig("{ tooNegative: " + (Integer.MIN_VALUE - 1L) + ", tooPositive: " + (Integer.MAX_VALUE + 1L) + "}")
|
||||
|
Loading…
Reference in New Issue
Block a user