Split ConfigSubstitution into ConfigConcatenation and ConfigReference

This makes the code a good bit simpler to reason about, the old
ConfigSubstitution really mixed two things into one class.

Unfortunately old ConfigSubstitution is hanging around as a compat
shim so we can deserialize stuff from the old version of the library.

The old version will not be able to deserialize unresolved configs
from this new version.

In retrospect it might have been better to forbid serialization
of unresolved configs and only support serializing resolved configs.
This commit is contained in:
Havoc Pennington 2012-03-31 22:09:05 -04:00
parent 27d92bec46
commit 006777c062
10 changed files with 688 additions and 198 deletions

View File

@ -0,0 +1,228 @@
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
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;
import com.typesafe.config.ConfigValueType;
/**
* A ConfigConcatenation represents a list of values to be concatenated (see the
* spec). It only has to exist if at least one value is an unresolved
* substitution, otherwise we could go ahead and collapse the list into a single
* value.
*
* Right now this is always a list of strings and ${} references, but in the
* future should support a list of ConfigList. We may also support
* concatenations of objects, but ConfigDelayedMerge should be used for that
* since a concat of objects really will merge, not concatenate.
*/
final class ConfigConcatenation extends AbstractConfigValue implements Unmergeable {
private static final long serialVersionUID = 1L;
final private List<AbstractConfigValue> pieces;
ConfigConcatenation(ConfigOrigin origin, List<AbstractConfigValue> pieces) {
super(origin);
this.pieces = pieces;
}
private ConfigException.NotResolved notResolved() {
return new ConfigException.NotResolved(
"need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: "
+ this);
}
@Override
public ConfigValueType valueType() {
throw notResolved();
}
@Override
public Object unwrapped() {
throw notResolved();
}
@Override
protected ConfigConcatenation newCopy(boolean ignoresFallbacks, ConfigOrigin newOrigin) {
return new ConfigConcatenation(newOrigin, pieces);
}
@Override
protected boolean ignoresFallbacks() {
// we can never ignore fallbacks because if a child ConfigReference
// is self-referential we have to look lower in the merge stack
// for its value.
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);
}
private static ResolveReplacer undefinedReplacer = new ResolveReplacer() {
@Override
protected AbstractConfigValue makeReplacement() throws Undefined {
throw new Undefined();
}
};
@Override
AbstractConfigValue resolveSubstitutions(ResolveContext context) throws NotPossibleToResolve {
List<AbstractConfigValue> resolved = new ArrayList<AbstractConfigValue>(pieces.size());
// if you have "foo = ${?foo}bar" then we will
// self-referentially look up foo and we need to
// get undefined, rather than "bar"
context.replace(this, undefinedReplacer);
try {
for (AbstractConfigValue p : pieces) {
// to concat into a string we have to do a full resolve,
// so unrestrict the context
AbstractConfigValue r = context.unrestricted().resolve(p);
if (r == null) {
// it was optional... omit
} else {
switch (r.valueType()) {
case LIST:
case OBJECT:
// cannot substitute lists and objects into strings
// we know p was a ConfigReference since it wasn't
// a ConfigString
String pathString = ((ConfigReference) p).expression().toString();
throw new ConfigException.WrongType(r.origin(), pathString, "not a list or object", r.valueType().name());
default:
resolved.add(r);
}
}
}
} finally {
context.unreplace(this);
}
// now need to concat everything
StringBuilder sb = new StringBuilder();
for (AbstractConfigValue r : resolved) {
sb.append(r.transformToString());
}
return new ConfigString(origin(), sb.toString());
}
@Override
ResolveStatus resolveStatus() {
return ResolveStatus.UNRESOLVED;
}
// when you graft a substitution into another object,
// you have to prefix it with the location in that object
// where you grafted it; but save prefixLength so
// system property and env variable lookups don't get
// broken.
@Override
ConfigConcatenation relativized(Path prefix) {
List<AbstractConfigValue> newPieces = new ArrayList<AbstractConfigValue>();
for (AbstractConfigValue p : pieces) {
newPieces.add(p.relativized(prefix));
}
return new ConfigConcatenation(origin(), newPieces);
}
@SuppressWarnings("deprecation")
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigConcatenation || other instanceof ConfigSubstitution;
}
@SuppressWarnings("deprecation")
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof ConfigConcatenation) {
return canEqual(other) && this.pieces.equals(((ConfigConcatenation) other).pieces);
} else if (other instanceof ConfigSubstitution) {
return equals(((ConfigSubstitution) other).delegate());
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return pieces.hashCode();
}
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
for (AbstractConfigValue p : pieces) {
p.render(sb, indent, formatted);
}
}
// This ridiculous hack is because some JDK versions apparently can't
// serialize an array, which is used to implement ArrayList and EmptyList.
// maybe
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627
private Object writeReplace() throws ObjectStreamException {
// switch to LinkedList
return new ConfigConcatenation(origin(), new java.util.LinkedList<AbstractConfigValue>(
pieces));
}
static List<AbstractConfigValue> valuesFromPieces(ConfigOrigin origin, List<Object> pieces) {
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>(pieces.size());
for (Object p : pieces) {
if (p instanceof SubstitutionExpression) {
values.add(new ConfigReference(origin, (SubstitutionExpression) p));
} else if (p instanceof String) {
values.add(new ConfigString(origin, (String) p));
} else {
throw new ConfigException.BugOrBroken("Unexpected piece " + p);
}
}
return values;
}
}

View File

@ -0,0 +1,165 @@
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;
import com.typesafe.config.ConfigValueType;
/**
* ConfigReference replaces ConfigReference (the older class kept for back
* compat) and represents the ${} substitution syntax. It can resolve to any
* kind of value.
*/
final class ConfigReference extends AbstractConfigValue implements Unmergeable {
private static final long serialVersionUID = 1L;
final private SubstitutionExpression expr;
// the length of any prefixes added with relativized()
final private int prefixLength;
ConfigReference(ConfigOrigin origin, SubstitutionExpression expr) {
this(origin, expr, 0);
}
private ConfigReference(ConfigOrigin origin, SubstitutionExpression expr, int prefixLength) {
super(origin);
this.expr = expr;
this.prefixLength = prefixLength;
}
private ConfigException.NotResolved notResolved() {
return new ConfigException.NotResolved(
"need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: "
+ this);
}
@Override
public ConfigValueType valueType() {
throw notResolved();
}
@Override
public Object unwrapped() {
throw notResolved();
}
@Override
protected ConfigReference newCopy(boolean ignoresFallbacks, ConfigOrigin newOrigin) {
if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("Cannot ignore fallbacks for " + this);
return new ConfigReference(newOrigin, expr, prefixLength);
}
@Override
protected boolean ignoresFallbacks() {
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);
}
@Override
AbstractConfigValue resolveSubstitutions(ResolveContext context) throws NotPossibleToResolve {
AbstractConfigValue v = context.source().lookupSubst(context, this, expr, prefixLength);
if (v == null && !expr.optional()) {
throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString());
}
return v;
}
@Override
ResolveStatus resolveStatus() {
return ResolveStatus.UNRESOLVED;
}
// when you graft a substitution into another object,
// you have to prefix it with the location in that object
// where you grafted it; but save prefixLength so
// system property and env variable lookups don't get
// broken.
@Override
ConfigReference relativized(Path prefix) {
SubstitutionExpression newExpr = expr.changePath(expr.path().prepend(prefix));
return new ConfigReference(origin(), newExpr, prefixLength + prefix.length());
}
@SuppressWarnings("deprecation")
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigReference || other instanceof ConfigSubstitution;
}
@SuppressWarnings("deprecation")
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof ConfigReference) {
return canEqual(other) && this.expr.equals(((ConfigReference) other).expr);
} else if (other instanceof ConfigSubstitution) {
return equals(((ConfigSubstitution) other).delegate());
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return expr.hashCode();
}
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append(expr.toString());
}
SubstitutionExpression expression() {
return expr;
}
}

View File

@ -3,10 +3,9 @@
*/
package com.typesafe.config.impl;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.typesafe.config.ConfigException;
@ -14,10 +13,11 @@ import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
/**
* A ConfigSubstitution represents a value with one or more substitutions in it;
* it can resolve to a value of any type, though if the substitution has more
* than one piece it always resolves to a string via value concatenation.
* ConfigSubstitution is now a shim for back-compat with serialization. i.e. an
* old version may unserialize one of these. We now split into ConfigReference
* and ConfigConcatenation.
*/
@Deprecated
final class ConfigSubstitution extends AbstractConfigValue implements
Unmergeable {
@ -27,224 +27,109 @@ final class ConfigSubstitution extends AbstractConfigValue implements
// SubstitutionExpression has to be resolved to values, then if there's more
// than one piece everything is stringified and concatenated
final private List<Object> pieces;
// the length of any prefixes added with relativized()
final private int prefixLength;
// this is just here to avoid breaking serialization;
// it is always false at the moment.
// this is just here to avoid breaking serialization
@SuppressWarnings("unused")
@Deprecated
final private int prefixLength = 0;
// this is just here to avoid breaking serialization
@SuppressWarnings("unused")
@Deprecated
final private boolean ignoresFallbacks = false;
ConfigSubstitution(ConfigOrigin origin, List<Object> pieces) {
this(origin, pieces, 0, false);
}
// we chain the ConfigSubstitution back-compat stub to a new value
private transient AbstractConfigValue delegate = null;
private ConfigSubstitution(ConfigOrigin origin, List<Object> pieces,
int prefixLength, boolean ignoresFallbacks) {
super(origin);
this.pieces = pieces;
this.prefixLength = prefixLength;
private void createDelegate() {
if (delegate != null)
throw new ConfigException.BugOrBroken("creating delegate twice: " + this);
for (Object p : pieces) {
if (p instanceof Path)
throw new RuntimeException("broken here");
List<AbstractConfigValue> values = ConfigConcatenation.valuesFromPieces(origin(), pieces);
if (values.size() == 1) {
delegate = values.get(0);
} else {
delegate = new ConfigConcatenation(origin(), values);
}
if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("ConfigSubstitution may never ignore fallbacks");
if (!(delegate instanceof Unmergeable))
throw new ConfigException.BugOrBroken("delegate must be Unmergeable: " + this
+ " delegate was: " + delegate);
}
private ConfigException.NotResolved notResolved() {
return new ConfigException.NotResolved(
"need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: "
+ this);
AbstractConfigValue delegate() {
if (delegate == null)
throw new NullPointerException("failed to create delegate " + this);
return delegate;
}
ConfigSubstitution(ConfigOrigin origin, List<Object> pieces) {
super(origin);
this.pieces = pieces;
createDelegate();
}
@Override
public ConfigValueType valueType() {
throw notResolved();
return delegate().valueType();
}
@Override
public Object unwrapped() {
throw notResolved();
return delegate().unwrapped();
}
@Override
protected ConfigSubstitution newCopy(boolean ignoresFallbacks, ConfigOrigin newOrigin) {
return new ConfigSubstitution(newOrigin, pieces, prefixLength, ignoresFallbacks);
protected AbstractConfigValue newCopy(boolean ignoresFallbacks, ConfigOrigin newOrigin) {
return delegate().newCopy(ignoresFallbacks, newOrigin);
}
@Override
protected boolean ignoresFallbacks() {
return ignoresFallbacks;
return delegate().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());
}
protected AbstractConfigValue mergedLater(AbstractConfigValue fallback) {
if (ignoresFallbacks)
throw new ConfigException.BugOrBroken("should not be reached");
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.add(this);
newStack.add(fallback);
return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
fallback.ignoresFallbacks());
return delegate().mergedWithTheUnmergeable(fallback);
}
@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);
return delegate().mergedWithObject(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);
return delegate().mergedWithNonObject(fallback);
}
@Override
public Collection<ConfigSubstitution> unmergedValues() {
return Collections.singleton(this);
}
List<Object> pieces() {
return pieces;
}
private static ResolveReplacer undefinedReplacer = new ResolveReplacer() {
@Override
protected AbstractConfigValue makeReplacement() throws Undefined {
throw new Undefined();
}
};
private AbstractConfigValue resolveValueConcat(ResolveContext context)
throws NotPossibleToResolve {
// need to concat everything into a string
StringBuilder sb = new StringBuilder();
for (Object p : pieces) {
if (p instanceof String) {
sb.append((String) p);
} else {
SubstitutionExpression exp = (SubstitutionExpression) p;
// to concat into a string we have to do a full resolve,
// so unrestrict the context
AbstractConfigValue v = context.source().lookupSubst(context.unrestricted(), this,
exp, prefixLength);
if (v == null) {
if (exp.optional()) {
// append nothing to StringBuilder
} else {
throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString());
}
} else {
switch (v.valueType()) {
case LIST:
case OBJECT:
// cannot substitute lists and objects into strings
throw new ConfigException.WrongType(v.origin(), exp.path().render(),
"not a list or object", v.valueType().name());
default:
sb.append(v.transformToString());
}
}
}
}
return new ConfigString(origin(), sb.toString());
}
private AbstractConfigValue resolveSingleSubst(ResolveContext context)
throws NotPossibleToResolve {
if (!(pieces.get(0) instanceof SubstitutionExpression))
throw new ConfigException.BugOrBroken(
"ConfigSubstitution should never contain a single String piece");
SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0);
AbstractConfigValue v = context.source().lookupSubst(context, this, exp,
prefixLength);
if (v == null && !exp.optional()) {
throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString());
}
return v;
public Collection<? extends AbstractConfigValue> unmergedValues() {
return ((Unmergeable) delegate()).unmergedValues();
}
@Override
AbstractConfigValue resolveSubstitutions(ResolveContext context)
throws NotPossibleToResolve {
AbstractConfigValue resolved;
if (pieces.size() > 1) {
// if you have "foo = ${?foo}bar" then we will
// self-referentially look up foo and we need to
// get undefined, rather than "bar"
context.replace(this, undefinedReplacer);
try {
resolved = resolveValueConcat(context);
} finally {
context.unreplace(this);
}
} else {
resolved = resolveSingleSubst(context);
}
return resolved;
AbstractConfigValue resolveSubstitutions(ResolveContext context) throws NotPossibleToResolve {
return context.resolve(delegate());
}
@Override
ResolveStatus resolveStatus() {
return ResolveStatus.UNRESOLVED;
return delegate().resolveStatus();
}
// when you graft a substitution into another object,
// you have to prefix it with the location in that object
// where you grafted it; but save prefixLength so
// system property and env variable lookups don't get
// broken.
@Override
ConfigSubstitution relativized(Path prefix) {
List<Object> newPieces = new ArrayList<Object>();
for (Object p : pieces) {
if (p instanceof SubstitutionExpression) {
SubstitutionExpression exp = (SubstitutionExpression) p;
newPieces.add(exp.changePath(exp.path().prepend(prefix)));
} else {
newPieces.add(p);
}
}
return new ConfigSubstitution(origin(), newPieces, prefixLength
+ prefix.length(), ignoresFallbacks);
AbstractConfigValue relativized(Path prefix) {
return delegate().relativized(prefix);
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigSubstitution;
return other instanceof ConfigSubstitution || other instanceof ConfigReference
|| other instanceof ConfigConcatenation;
}
@Override
@ -254,25 +139,18 @@ final class ConfigSubstitution extends AbstractConfigValue implements
return canEqual(other)
&& this.pieces.equals(((ConfigSubstitution) other).pieces);
} else {
return false;
return delegate().equals(other);
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return pieces.hashCode();
return delegate().hashCode();
}
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
for (Object p : pieces) {
if (p instanceof SubstitutionExpression) {
sb.append(p.toString());
} else {
sb.append(ConfigImplUtil.renderJsonString((String) p));
}
}
delegate().render(sb, indent, formatted);
}
// This ridiculous hack is because some JDK versions apparently can't
@ -281,7 +159,20 @@ final class ConfigSubstitution extends AbstractConfigValue implements
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627
private Object writeReplace() throws ObjectStreamException {
// switch to LinkedList
return new ConfigSubstitution(origin(), new java.util.LinkedList<Object>(pieces),
prefixLength, ignoresFallbacks);
return new ConfigSubstitution(origin(), new java.util.LinkedList<Object>(pieces));
}
// generate the delegate when we deserialize to avoid thread safety
// issues later
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
createDelegate();
}
// this is a little cleaner but just causes compat issues probably
// private Object readResolve() throws ObjectStreamException {
// replace ourselves on deserialize
// return delegate();
// }
}

View File

@ -335,10 +335,15 @@ final class Parser {
if (minimized.size() == 1 && minimized.get(0) instanceof String) {
consolidated = Tokens.newString(firstOrigin,
(String) minimized.get(0));
} else if (minimized.size() == 1 && minimized.get(0) instanceof SubstitutionExpression) {
// a substitution expression ${}
consolidated = Tokens.newValue(new ConfigReference(firstOrigin,
(SubstitutionExpression) minimized.get(0)));
} else {
// there's some substitution to do later (post-parse step)
consolidated = Tokens.newValue(new ConfigSubstitution(
firstOrigin, minimized));
// a value concatenation with a substitution expression in it
List<AbstractConfigValue> vs = ConfigConcatenation.valuesFromPieces(
firstOrigin, minimized);
consolidated = Tokens.newValue(new ConfigConcatenation(firstOrigin, vs));
}
putBack(new TokenWithComments(consolidated, firstValueWithComments.comments));

View File

@ -51,7 +51,7 @@ final class ResolveContext {
Collections.singletonList(new LinkedHashSet<MemoKey>())), options, restrictToChild);
}
private void traverse(ConfigSubstitution value, SubstitutionExpression via)
private void traverse(ConfigReference value, SubstitutionExpression via)
throws SelfReferential {
Set<MemoKey> traversed = traversedStack.peekFirst();
@ -63,7 +63,7 @@ final class ResolveContext {
traversed.add(key);
}
private void untraverse(ConfigSubstitution value) {
private void untraverse(ConfigReference value) {
Set<MemoKey> traversed = traversedStack.peekFirst();
MemoKey key = new MemoKey(value, restrictToChild);
@ -78,7 +78,7 @@ final class ResolveContext {
AbstractConfigValue call() throws NotPossibleToResolve;
}
AbstractConfigValue traversing(ConfigSubstitution value, SubstitutionExpression subst,
AbstractConfigValue traversing(ConfigReference value, SubstitutionExpression subst,
Resolver callable) throws NotPossibleToResolve {
try {
traverse(value, subst);

View File

@ -26,7 +26,7 @@ final class ResolveSource {
}
static private AbstractConfigValue findInObject(final AbstractConfigObject obj,
final ResolveContext context, ConfigSubstitution traversed,
final ResolveContext context, ConfigReference traversed,
final SubstitutionExpression subst) throws NotPossibleToResolve {
return context.traversing(traversed, subst, new ResolveContext.Resolver() {
@Override
@ -36,7 +36,7 @@ final class ResolveSource {
});
}
AbstractConfigValue lookupSubst(final ResolveContext context, ConfigSubstitution traversed,
AbstractConfigValue lookupSubst(final ResolveContext context, ConfigReference traversed,
final SubstitutionExpression subst, int prefixLength) throws NotPossibleToResolve {
// First we look up the full path, which means relative to the
// included file if we were not a root file

View File

@ -77,10 +77,8 @@ class ConfParserTest extends TestUtils {
tree match {
case list: ConfigList =>
list.get(0) match {
case subst: ConfigSubstitution =>
subst.pieces().get(0) match {
case exp: SubstitutionExpression => exp.path()
}
case ref: ConfigReference =>
ref.expression().path()
}
}
} catch {

View File

@ -705,7 +705,7 @@ class ConfigSubstitutionTest extends TestUtils {
}
@Test
def serializeUnresolvedObject() {
def deserializeOldUnresolvedObject() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674f626a65637400000000000000010200035a001069676e6f72657346616c6c6261636b" +
@ -784,6 +784,83 @@ class ConfigSubstitutionTest extends TestUtils {
"070000000c0000000c7071007e000c71007e000f7000000000007371007e001a7704000000017371" +
"007e001c007371007e001f740008707472546f4172727078787878"
checkSerializableOldFormat(expectedSerialization, substComplexObject)
}
@Test
def serializeUnresolvedObject() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674f626a65637400000000000000010200035a001069676e6f72657346616c6c6261636b" +
"735a00087265736f6c7665644c000576616c756574000f4c6a6176612f7574696c2f4d61703b7872" +
"002d636f6d2e74797065736166652e636f6e6669672e696d706c2e4162737472616374436f6e6669" +
"674f626a65637400000000000000010200014c0006636f6e6669677400274c636f6d2f7479706573" +
"6166652f636f6e6669672f696d706c2f53696d706c65436f6e6669673b7872002c636f6d2e747970" +
"65736166652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c7565000000" +
"00000000010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e666967" +
"2f696d706c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e7479706573" +
"6166652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e000000000000" +
"000102000649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d" +
"6d656e74734f724e756c6c7400104c6a6176612f7574696c2f4c6973743b4c000b64657363726970" +
"74696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967696e547970657400" +
"254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f726967696e547970653b4c00" +
"0975726c4f724e756c6c71007e0009787000000002000000027074000b7465737420737472696e67" +
"7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f726967696e5479706500" +
"000000000000001200007872000e6a6176612e6c616e672e456e756d000000000000000012000078" +
"7074000747454e455249437073720025636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e53696d706c65436f6e66696700000000000000010200014c00066f626a65637474002f4c636f6d" +
"2f74797065736166652f636f6e6669672f696d706c2f4162737472616374436f6e6669674f626a65" +
"63743b787071007e00060000737200116a6176612e7574696c2e486173684d61700507dac1c31660" +
"d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c77" +
"08000000100000000a7400046f626a4573720028636f6d2e74797065736166652e636f6e6669672e" +
"696d706c2e436f6e6669675265666572656e6365000000000000000102000249000c707265666978" +
"4c656e6774684c0004657870727400314c636f6d2f74797065736166652f636f6e6669672f696d70" +
"6c2f537562737469747574696f6e45787072657373696f6e3b7871007e00047371007e0007000000" +
"08000000087071007e000c71007e000f70000000007372002f636f6d2e74797065736166652e636f" +
"6e6669672e696d706c2e537562737469747574696f6e45787072657373696f6e0000000000000001" +
"0200025a00086f7074696f6e616c4c00047061746874001f4c636f6d2f74797065736166652f636f" +
"6e6669672f696d706c2f506174683b7870007372001d636f6d2e74797065736166652e636f6e6669" +
"672e696d706c2e5061746800000000000000010200024c0005666972737471007e00094c00097265" +
"6d61696e64657271007e001c7870740001617371007e001e740001627371007e001e740001657074" +
"0007666f6f2e62617273720022636f6d2e74797065736166652e636f6e6669672e696d706c2e436f" +
"6e666967496e74000000000000000102000149000576616c756578720025636f6d2e747970657361" +
"66652e636f6e6669672e696d706c2e436f6e6669674e756d62657200000000000000010200014c00" +
"0c6f726967696e616c5465787471007e00097871007e00047371007e000700000009000000097071" +
"007e000c71007e000f707400023337000000257400046f626a427371007e00177371007e00070000" +
"0007000000077071007e000c71007e000f70000000007371007e001b007371007e001e7400016173" +
"71007e001e740001627074000361727273720029636f6d2e74797065736166652e636f6e6669672e" +
"696d706c2e53696d706c65436f6e6669674c69737400000000000000010200025a00087265736f6c" +
"7665644c000576616c756571007e00087871007e00047371007e00070000000a0000000a7071007e" +
"000c71007e000f7000737200146a6176612e7574696c2e4c696e6b65644c6973740c29535d4a6088" +
"2203000078707704000000067371007e00177371007e00070000000a0000000a7071007e000c7100" +
"7e000f70000000007371007e001b007371007e001e740003666f6f707371007e001771007e003a00" +
"0000007371007e001b007371007e001e740001617371007e001e740001627371007e001e74000163" +
"707371007e001771007e003a000000007371007e001b007371007e001e740007666f6f2e62617270" +
"7371007e001771007e003a000000007371007e001b007371007e001e7400046f626a427371007e00" +
"1e74000164707371007e001771007e003a000000007371007e001b007371007e001e7400046f626a" +
"417371007e001e740001627371007e001e740001657371007e001e74000166707371007e00177100" +
"7e003a000000007371007e001b007371007e001e7400046f626a457371007e001e74000166707874" +
"00046f626a417371007e00177371007e000700000006000000067071007e000c71007e000f700000" +
"00007371007e001b007371007e001e7400016170740001617371007e00007371007e000700000005" +
"000000057071007e000c71007e000f707371007e001171007e006700007371007e00143f40000000" +
"00000c77080000001000000001740001627371007e00007371007e00070000000500000005707100" +
"7e000c71007e000f707371007e001171007e006c00007371007e00143f4000000000000c77080000" +
"001000000003740001647371007e00177371007e000700000005000000057071007e000c71007e00" +
"0f70000000007371007e001b007371007e001e740003666f6f70740001657371007e00007371007e" +
"000700000005000000057071007e000c71007e000f707371007e001171007e007700007371007e00" +
"143f4000000000000c77080000001000000001740001667371007e001771007e0072000000007371" +
"007e001b007371007e001e740003666f6f7078740001637371007e002671007e0072740002353700" +
"0000397878740003666f6f7371007e00177371007e000700000003000000037071007e000c71007e" +
"000f70000000007371007e001b007371007e001e74000362617270740008707472546f4172727371" +
"007e00177371007e00070000000b0000000b7071007e000c71007e000f70000000007371007e001b" +
"007371007e001e740003617272707400036261727371007e00177371007e00070000000400000004" +
"7071007e000c71007e000f70000000007371007e001b007371007e001e740001617371007e001e74" +
"0001627371007e001e7400016370740001787371007e00007371007e00070000000c0000000c7071" +
"007e000c71007e000f707371007e001171007e009a00007371007e00143f4000000000000c770800" +
"00001000000001740001797371007e00007371007e00070000000c0000000c7071007e000c71007e" +
"000f707371007e001171007e009f00007371007e00143f4000000000000c77080000001000000001" +
"74000d707472546f507472546f4172727371007e00177371007e00070000000c0000000c7071007e" +
"000c71007e000f70000000007371007e001b007371007e001e740008707472546f41727270787878"
checkSerializable(expectedSerialization, substComplexObject)
}

View File

@ -237,6 +237,9 @@ class ConfigValueTest extends TestUtils {
val sameAsA = subst("foo")
val b = subst("bar")
assertTrue("wrong type " + a, a.isInstanceOf[ConfigSubstitution])
assertTrue("wrong type " + b, b.isInstanceOf[ConfigSubstitution])
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
checkNotEqualObjects(a, b)
@ -271,6 +274,122 @@ class ConfigValueTest extends TestUtils {
val b = checkSerializable(expectedSerialization, a)
}
@Test
def configReferenceEquality() {
val a = subst("foo").delegate()
val sameAsA = subst("foo").delegate()
val b = subst("bar").delegate()
val c = subst("foo", optional = true).delegate()
assertTrue("wrong type " + a, a.isInstanceOf[ConfigReference])
assertTrue("wrong type " + b, b.isInstanceOf[ConfigReference])
assertTrue("wrong type " + c, c.isInstanceOf[ConfigReference])
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
checkNotEqualObjects(a, b)
checkNotEqualObjects(a, c)
}
@Test
def configReferenceSerializable() {
val expectedSerialization = "" +
"aced000573720028636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696752" +
"65666572656e6365000000000000000102000249000c7072656669784c656e6774684c0004657870" +
"727400314c636f6d2f74797065736166652f636f6e6669672f696d706c2f53756273746974757469" +
"6f6e45787072657373696f6e3b7872002c636f6d2e74797065736166652e636f6e6669672e696d70" +
"6c2e4162737472616374436f6e66696756616c756500000000000000010200014c00066f72696769" +
"6e74002d4c636f6d2f74797065736166652f636f6e6669672f696d706c2f53696d706c65436f6e66" +
"69674f726967696e3b78707372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e" +
"53696d706c65436f6e6669674f726967696e000000000000000102000649000d656e644c696e654e" +
"756d62657249000a6c696e654e756d6265724c000e636f6d6d656e74734f724e756c6c7400104c6a" +
"6176612f7574696c2f4c6973743b4c000b6465736372697074696f6e7400124c6a6176612f6c616e" +
"672f537472696e673b4c000a6f726967696e547970657400254c636f6d2f74797065736166652f63" +
"6f6e6669672f696d706c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e000778" +
"70ffffffffffffffff7074000b66616b65206f726967696e7e720023636f6d2e7479706573616665" +
"2e636f6e6669672e696d706c2e4f726967696e5479706500000000000000001200007872000e6a61" +
"76612e6c616e672e456e756d0000000000000000120000787074000747454e455249437000000000" +
"7372002f636f6d2e74797065736166652e636f6e6669672e696d706c2e537562737469747574696f" +
"6e45787072657373696f6e00000000000000010200025a00086f7074696f6e616c4c000470617468" +
"74001f4c636f6d2f74797065736166652f636f6e6669672f696d706c2f506174683b787000737200" +
"1d636f6d2e74797065736166652e636f6e6669672e696d706c2e5061746800000000000000010200" +
"024c0005666972737471007e00074c000972656d61696e64657271007e00107870740003666f6f70"
val a = subst("foo").delegate()
assertTrue("wrong type " + a, a.isInstanceOf[ConfigReference])
val b = checkSerializable(expectedSerialization, a)
assertTrue("wrong type " + b, b.isInstanceOf[ConfigReference])
}
@Test
def configConcatenationEquality() {
val a = substInString("foo").delegate()
val sameAsA = substInString("foo").delegate()
val b = substInString("bar").delegate()
val c = substInString("foo", optional = true).delegate()
assertTrue("wrong type " + a, a.isInstanceOf[ConfigConcatenation])
assertTrue("wrong type " + b, b.isInstanceOf[ConfigConcatenation])
assertTrue("wrong type " + c, c.isInstanceOf[ConfigConcatenation])
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
checkNotEqualObjects(a, b)
checkNotEqualObjects(a, c)
}
@Test
def configConcatenationSerializable() {
val expectedSerialization = "" +
"aced00057372002c636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696743" +
"6f6e636174656e6174696f6e00000000000000010200014c00067069656365737400104c6a617661" +
"2f7574696c2f4c6973743b7872002c636f6d2e74797065736166652e636f6e6669672e696d706c2e" +
"4162737472616374436f6e66696756616c756500000000000000010200014c00066f726967696e74" +
"002d4c636f6d2f74797065736166652f636f6e6669672f696d706c2f53696d706c65436f6e666967" +
"4f726967696e3b78707372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e5369" +
"6d706c65436f6e6669674f726967696e000000000000000102000649000d656e644c696e654e756d" +
"62657249000a6c696e654e756d6265724c000e636f6d6d656e74734f724e756c6c71007e00014c00" +
"0b6465736372697074696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967" +
"696e547970657400254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f72696769" +
"6e547970653b4c000975726c4f724e756c6c71007e00067870ffffffffffffffff7074000b66616b" +
"65206f726967696e7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f7269" +
"67696e5479706500000000000000001200007872000e6a6176612e6c616e672e456e756d00000000" +
"00000000120000787074000747454e4552494370737200146a6176612e7574696c2e4c696e6b6564" +
"4c6973740c29535d4a608822030000787077040000000373720025636f6d2e74797065736166652e" +
"636f6e6669672e696d706c2e436f6e666967537472696e6700000000000000010200014c00057661" +
"6c756571007e00067871007e000271007e000874000673746172743c73720028636f6d2e74797065" +
"736166652e636f6e6669672e696d706c2e436f6e6669675265666572656e63650000000000000001" +
"02000249000c7072656669784c656e6774684c0004657870727400314c636f6d2f74797065736166" +
"652f636f6e6669672f696d706c2f537562737469747574696f6e45787072657373696f6e3b787100" +
"7e000271007e0008000000007372002f636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e537562737469747574696f6e45787072657373696f6e00000000000000010200025a00086f7074" +
"696f6e616c4c00047061746874001f4c636f6d2f74797065736166652f636f6e6669672f696d706c" +
"2f506174683b7870007372001d636f6d2e74797065736166652e636f6e6669672e696d706c2e5061" +
"746800000000000000010200024c0005666972737471007e00064c000972656d61696e6465727100" +
"7e00177870740003666f6f707371007e001071007e00087400043e656e6478"
val a = substInString("foo").delegate()
assertTrue("wrong type " + a, a.isInstanceOf[ConfigConcatenation])
val b = checkSerializable(expectedSerialization, a)
assertTrue("wrong type " + b, b.isInstanceOf[ConfigConcatenation])
}
@Test
def configSubstitutionEqualsItsDelegates() {
val a = subst("foo")
assertTrue("wrong type " + a, a.isInstanceOf[ConfigSubstitution])
val aD = a.delegate()
assertTrue("wrong type " + aD, aD.isInstanceOf[ConfigReference])
val b = substInString("bar")
assertTrue("wrong type " + b, b.isInstanceOf[ConfigSubstitution])
val bD = b.delegate()
assertTrue("wrong type " + bD, bD.isInstanceOf[ConfigConcatenation])
checkEqualObjects(a, aD)
checkEqualObjects(b, bD)
}
@Test
def configDelayedMergeEquality() {
val s1 = subst("foo")

View File

@ -103,7 +103,7 @@ abstract trait TestUtils {
copy
}
protected def checkSerializationCompat[T: Manifest](expectedHex: String, o: T): Unit = {
protected def checkSerializationCompat[T: Manifest](expectedHex: String, o: T, changedOK: Boolean = false): Unit = {
// be sure we can still deserialize the old one
val inStream = new ByteArrayInputStream(Hex.decodeHex(expectedHex.toCharArray()))
val inObjectStream = new ObjectInputStream(inStream)
@ -146,8 +146,9 @@ abstract trait TestUtils {
o, deserialized)
assertFalse(failure.isDefined) // should have thrown if we had a failure
assertEquals(o.getClass.getSimpleName + " serialization has changed (though we still deserialized the old serialization)",
expectedHex, hex)
if (!changedOK)
assertEquals(o.getClass.getSimpleName + " serialization has changed (though we still deserialized the old serialization)",
expectedHex, hex)
} catch {
case e: Throwable =>
showCorrectResult()
@ -161,6 +162,12 @@ abstract trait TestUtils {
t
}
protected def checkSerializableOldFormat[T: Manifest](expectedHex: String, o: T): T = {
val t = checkSerializable(o)
checkSerializationCompat(expectedHex, o, changedOK = true)
t
}
protected def checkSerializable[T: Manifest](o: T): T = {
checkEqualObjects(o, o)