Make ResolveContext immutable

This is a mechanical refactoring not intended to change behavior.
Not very efficiently implemented but that's fine for now.
This commit is contained in:
Havoc Pennington 2014-10-13 11:23:07 -04:00
parent afdcbb3803
commit c8f42ef92d
12 changed files with 288 additions and 155 deletions

View File

@ -177,7 +177,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi
}
@Override
abstract AbstractConfigObject resolveSubstitutions(ResolveContext context, ResolveSource source)
abstract ResolveResult<? extends AbstractConfigObject> resolveSubstitutions(ResolveContext context,
ResolveSource source)
throws NotPossibleToResolve;
@Override

View File

@ -72,9 +72,9 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
* where to look up values
* @return a new value if there were changes, or this if no changes
*/
AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source)
ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
return this;
return ResolveResult.make(context, this);
}
ResolveStatus resolveStatus() {

View File

@ -170,7 +170,8 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
}
@Override
AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve {
ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
if (ConfigImpl.traceSubstitutionsEnabled()) {
int indent = context.depth() + 2;
ConfigImpl.trace(indent - 1, "concatenation has " + pieces.size() + " pieces:");
@ -185,12 +186,17 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
// content of ConfigConcatenation should not need to replaceChild,
// but if it did we'd have to do this.
ResolveSource sourceWithParent = source; // .pushParent(this);
ResolveContext newContext = context;
List<AbstractConfigValue> resolved = new ArrayList<AbstractConfigValue>(pieces.size());
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, sourceWithParent);
// so unrestrict the context, then put restriction back afterward
Path restriction = newContext.restrictToChild();
ResolveResult<? extends AbstractConfigValue> result = newContext.unrestricted()
.resolve(p, sourceWithParent);
AbstractConfigValue r = result.value;
newContext = result.context.restrict(restriction);
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "resolved concat piece to " + r);
if (r == null) {
@ -205,11 +211,12 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
// if unresolved is allowed we can just become another
// ConfigConcatenation
if (joined.size() > 1 && context.options().getAllowUnresolved())
return new ConfigConcatenation(this.origin(), joined);
return ResolveResult.make(newContext, new ConfigConcatenation(this.origin(), joined));
else if (joined.isEmpty())
return null; // we had just a list of optional references using ${?}
// we had just a list of optional references using ${?}
return ResolveResult.make(newContext, null);
else if (joined.size() == 1)
return joined.get(0);
return ResolveResult.make(newContext, joined.get(0));
else
throw new ConfigException.BugOrBroken("Bug in the library; resolved list was joined to too many values: "
+ joined);

View File

@ -54,13 +54,14 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
}
@Override
AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source)
ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
return resolveSubstitutions(this, stack, context, source);
}
// static method also used by ConfigDelayedMergeObject
static AbstractConfigValue resolveSubstitutions(ReplaceableMergeStack replaceable, List<AbstractConfigValue> stack,
static ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(ReplaceableMergeStack replaceable,
List<AbstractConfigValue> stack,
ResolveContext context, ResolveSource source) throws NotPossibleToResolve {
if (ConfigImpl.traceSubstitutionsEnabled()) {
ConfigImpl.trace(context.depth(), "delayed merge stack has " + stack.size() + " items:");
@ -77,6 +78,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
// is non-null, or resolve options allow partial resolves,
// we may remain a delayed merge though.
ResolveContext newContext = context;
int count = 0;
AbstractConfigValue merged = null;
for (AbstractConfigValue end : stack) {
@ -92,7 +94,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
AbstractConfigValue remainder = replaceable.makeReplacement(context, count + 1);
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "remainder portion: " + remainder);
ConfigImpl.trace(newContext.depth(), "remainder portion: " + remainder);
// If, while resolving 'end' we come back to the same
// merge stack, we only want to look _below_ 'end'
@ -101,40 +103,43 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
// the remainder of the stack below this one.
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "building sourceForEnd");
ConfigImpl.trace(newContext.depth(), "building sourceForEnd");
// we resetParents() here because we'll be resolving "end"
// against a root which does NOT contain "end"
sourceForEnd = source.replaceWithinCurrentParent((AbstractConfigValue) replaceable, remainder);
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), " sourceForEnd before reset parents but after replace: "
ConfigImpl.trace(newContext.depth(), " sourceForEnd before reset parents but after replace: "
+ sourceForEnd);
sourceForEnd = sourceForEnd.resetParents();
} else {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl
.trace(context.depth(), "will resolve end against the original source with parent pushed");
.trace(newContext.depth(),
"will resolve end against the original source with parent pushed");
sourceForEnd = source.pushParent(replaceable);
}
if (ConfigImpl.traceSubstitutionsEnabled()) {
ConfigImpl.trace(context.depth(), "sourceForEnd =" + sourceForEnd);
ConfigImpl.trace(newContext.depth(), "sourceForEnd =" + sourceForEnd);
}
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "Resolving highest-priority item in delayed merge " + end
ConfigImpl.trace(newContext.depth(), "Resolving highest-priority item in delayed merge " + end
+ " against " + sourceForEnd + " endWasRemoved=" + (source != sourceForEnd));
AbstractConfigValue resolvedEnd = context.resolve(end, sourceForEnd);
ResolveResult<? extends AbstractConfigValue> result = newContext.resolve(end, sourceForEnd);
AbstractConfigValue resolvedEnd = result.value;
newContext = result.context;
if (resolvedEnd != null) {
if (merged == null) {
merged = resolvedEnd;
} else {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd);
ConfigImpl.trace(newContext.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd);
merged = merged.withFallback(resolvedEnd);
}
}
@ -142,10 +147,10 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
count += 1;
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "stack merged, yielding: " + merged);
ConfigImpl.trace(newContext.depth(), "stack merged, yielding: " + merged);
}
return merged;
return ResolveResult.make(newContext, merged);
}
@Override

View File

@ -50,14 +50,11 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
}
@Override
AbstractConfigObject resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve {
AbstractConfigValue merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, context, source);
if (merged instanceof AbstractConfigObject) {
return (AbstractConfigObject) merged;
} else {
throw new ConfigException.BugOrBroken(
"somehow brokenly merged an object and didn't get an object, got " + merged);
}
ResolveResult<? extends AbstractConfigObject> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
ResolveResult<? extends AbstractConfigValue> merged = ConfigDelayedMerge.resolveSubstitutions(this, stack,
context, source);
return merged.asObjectResult();
}
@Override

View File

@ -65,49 +65,49 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
// This way it's impossible for NotPossibleToResolve to "escape" since
// any failure to resolve has to start with a ConfigReference.
@Override
AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source) {
context.addCycleMarker(this);
ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(ResolveContext context, ResolveSource source) {
ResolveContext newContext = context.addCycleMarker(this);
AbstractConfigValue v;
try {
AbstractConfigValue v;
try {
ResolveSource.ValueWithPath valueWithPath = source.lookupSubst(context, expr, prefixLength);
ResolveSource.ResultWithPath resultWithPath = source.lookupSubst(newContext, expr, prefixLength);
newContext = resultWithPath.result.context;
if (valueWithPath.value != null) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "recursively resolving " + valueWithPath
+ " which was the resolution of " + expr + " against " + source);
ResolveSource recursiveResolveSource = (new ResolveSource(
(AbstractConfigObject) valueWithPath.pathFromRoot.last(), valueWithPath.pathFromRoot));
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "will recursively resolve against " + recursiveResolveSource);
v = context.resolve(valueWithPath.value, recursiveResolveSource);
} else {
v = null;
}
} catch (NotPossibleToResolve e) {
if (resultWithPath.result.value != null) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(),
"not possible to resolve " + expr + ", cycle involved: " + e.traceString());
if (expr.optional())
v = null;
else
throw new ConfigException.UnresolvedSubstitution(origin(), expr
+ " was part of a cycle of substitutions involving " + e.traceString(), e);
}
ConfigImpl.trace(newContext.depth(), "recursively resolving " + resultWithPath
+ " which was the resolution of " + expr + " against " + source);
if (v == null && !expr.optional()) {
if (context.options().getAllowUnresolved())
return this;
else
throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString());
ResolveSource recursiveResolveSource = (new ResolveSource(
(AbstractConfigObject) resultWithPath.pathFromRoot.last(), resultWithPath.pathFromRoot));
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(newContext.depth(), "will recursively resolve against " + recursiveResolveSource);
ResolveResult<? extends AbstractConfigValue> result = newContext.resolve(resultWithPath.result.value,
recursiveResolveSource);
v = result.value;
newContext = result.context;
} else {
return v;
v = null;
}
} finally {
context.removeCycleMarker(this);
} catch (NotPossibleToResolve e) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(newContext.depth(),
"not possible to resolve " + expr + ", cycle involved: " + e.traceString());
if (expr.optional())
v = null;
else
throw new ConfigException.UnresolvedSubstitution(origin(), expr
+ " was part of a cycle of substitutions involving " + e.traceString(), e);
}
if (v == null && !expr.optional()) {
if (newContext.options().getAllowUnresolved())
return ResolveResult.make(newContext.removeCycleMarker(this), this);
else
throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString());
} else {
return ResolveResult.make(newContext.removeCycleMarker(this), v);
}
}

View File

@ -11,8 +11,6 @@ import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve;
final class ResolveContext {
// this is unfortunately mutable so should only be shared among
// ResolveContext in the same traversal.
final private ResolveMemos memos;
final private ConfigResolveOptions options;
@ -34,31 +32,46 @@ final class ResolveContext {
this.memos = memos;
this.options = options;
this.restrictToChild = restrictToChild;
this.resolveStack = resolveStack;
this.cycleMarkers = cycleMarkers;
this.resolveStack = Collections.unmodifiableList(resolveStack);
this.cycleMarkers = Collections.unmodifiableSet(cycleMarkers);
}
private static Set<AbstractConfigValue> newCycleMarkers() {
return Collections.newSetFromMap(new IdentityHashMap<AbstractConfigValue, Boolean>());
}
ResolveContext(ConfigResolveOptions options, Path restrictToChild) {
// LinkedHashSet keeps the traversal order which is at least useful
// in error messages if nothing else
this(new ResolveMemos(), options, restrictToChild, new ArrayList<AbstractConfigValue>(), Collections
.newSetFromMap(new IdentityHashMap<AbstractConfigValue, Boolean>()));
this(new ResolveMemos(), options, restrictToChild, new ArrayList<AbstractConfigValue>(), newCycleMarkers());
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "ResolveContext restrict to child " + restrictToChild);
}
void addCycleMarker(AbstractConfigValue value) {
ResolveContext addCycleMarker(AbstractConfigValue value) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "++ Cycle marker " + value + "@" + System.identityHashCode(value));
if (cycleMarkers.contains(value))
throw new ConfigException.BugOrBroken("Added cycle marker twice " + value);
cycleMarkers.add(value);
Set<AbstractConfigValue> copy = newCycleMarkers();
copy.addAll(cycleMarkers);
copy.add(value);
return new ResolveContext(memos, options, restrictToChild, resolveStack, copy);
}
void removeCycleMarker(AbstractConfigValue value) {
cycleMarkers.remove(value);
ResolveContext removeCycleMarker(AbstractConfigValue value) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "-- Cycle marker " + value + "@" + System.identityHashCode(value));
Set<AbstractConfigValue> copy = newCycleMarkers();
copy.addAll(cycleMarkers);
copy.remove(value);
return new ResolveContext(memos, options, restrictToChild, resolveStack, copy);
}
private ResolveContext memoize(MemoKey key, AbstractConfigValue value) {
ResolveMemos changed = memos.put(key, value);
return new ResolveContext(changed, options, restrictToChild, resolveStack, cycleMarkers);
}
ConfigResolveOptions options() {
@ -73,6 +86,7 @@ final class ResolveContext {
return restrictToChild;
}
// restrictTo may be null to unrestrict
ResolveContext restrict(Path restrictTo) {
if (restrictTo == restrictToChild)
return this;
@ -98,16 +112,20 @@ final class ResolveContext {
return sb.toString();
}
private void pushTrace(AbstractConfigValue value) {
private ResolveContext pushTrace(AbstractConfigValue value) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "pushing trace " + value);
resolveStack.add(value);
List<AbstractConfigValue> copy = new ArrayList<AbstractConfigValue>(resolveStack);
copy.add(value);
return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers);
}
private void popTrace() {
AbstractConfigValue old = resolveStack.remove(resolveStack.size() - 1);
ResolveContext popTrace() {
List<AbstractConfigValue> copy = new ArrayList<AbstractConfigValue>(resolveStack);
AbstractConfigValue old = copy.remove(resolveStack.size() - 1);
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "popped trace " + old);
ConfigImpl.trace(depth() - 1, "popped trace " + old);
return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers);
}
int depth() {
@ -116,21 +134,15 @@ final class ResolveContext {
return resolveStack.size();
}
AbstractConfigValue resolve(AbstractConfigValue original, ResolveSource source) throws NotPossibleToResolve {
ResolveResult<? extends AbstractConfigValue> resolve(AbstractConfigValue original, ResolveSource source)
throws NotPossibleToResolve {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl
.trace(depth(), "resolving " + original + " restrictToChild=" + restrictToChild + " in " + source);
AbstractConfigValue resolved;
pushTrace(original);
try {
resolved = realResolve(original, source);
} finally {
popTrace();
}
return resolved;
return pushTrace(original).realResolve(original, source).popTrace();
}
private AbstractConfigValue realResolve(AbstractConfigValue original, ResolveSource source)
private ResolveResult<? extends AbstractConfigValue> realResolve(AbstractConfigValue original, ResolveSource source)
throws NotPossibleToResolve {
// a fully-resolved (no restrictToChild) object can satisfy a
// request for a restricted object, so always check that first.
@ -151,7 +163,7 @@ final class ResolveContext {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "using cached resolution " + cached + " for " + original
+ " restrictToChild " + restrictToChild());
return cached;
return ResolveResult.make(this, cached);
} else {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(),
@ -164,12 +176,15 @@ final class ResolveContext {
throw new NotPossibleToResolve(this);
}
AbstractConfigValue resolved = original.resolveSubstitutions(this, source);
ResolveResult<? extends AbstractConfigValue> result = original.resolveSubstitutions(this, source);
AbstractConfigValue resolved = result.value;
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "resolved to " + resolved + "@" + System.identityHashCode(resolved)
+ " from " + original + "@" + System.identityHashCode(resolved));
ResolveContext withMemo = result.context;
if (resolved == null || resolved.resolveStatus() == ResolveStatus.RESOLVED) {
// if the resolved object is fully resolved by resolving
// only the restrictToChildOrNull, then it can be cached
@ -178,7 +193,7 @@ final class ResolveContext {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved);
memos.put(fullKey, resolved);
withMemo = withMemo.memoize(fullKey, resolved);
} else {
// if we have an unresolved object then either we did a
// partial resolve restricted to a certain child, or we are
@ -191,19 +206,19 @@ final class ResolveContext {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "caching " + restrictedKey + " result " + resolved);
memos.put(restrictedKey, resolved);
withMemo = withMemo.memoize(restrictedKey, resolved);
} else if (options().getAllowUnresolved()) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved);
memos.put(fullKey, resolved);
withMemo = withMemo.memoize(fullKey, resolved);
} else {
throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
}
}
return resolved;
return ResolveResult.make(withMemo, resolved);
}
}
@ -213,7 +228,7 @@ final class ResolveContext {
ResolveContext context = new ResolveContext(options, null /* restrictToChild */);
try {
return context.resolve(value, source);
return context.resolve(value, source).value;
} catch (NotPossibleToResolve e) {
// ConfigReference was supposed to catch NotPossibleToResolve
throw new ConfigException.BugOrBroken(

View File

@ -13,15 +13,23 @@ final class ResolveMemos {
// rather than ConfigNull) so this map can have null values.
final private Map<MemoKey, AbstractConfigValue> memos;
private ResolveMemos(Map<MemoKey, AbstractConfigValue> memos) {
this.memos = memos;
}
ResolveMemos() {
this.memos = new HashMap<MemoKey, AbstractConfigValue>();
this(new HashMap<MemoKey, AbstractConfigValue>());
}
AbstractConfigValue get(MemoKey key) {
return memos.get(key);
}
void put(MemoKey key, AbstractConfigValue value) {
memos.put(key, value);
ResolveMemos put(MemoKey key, AbstractConfigValue value) {
// completely inefficient, but so far nobody cares about resolve()
// performance, we can clean it up someday...
Map<MemoKey, AbstractConfigValue> copy = new HashMap<MemoKey, AbstractConfigValue>(memos);
copy.put(key, value);
return new ResolveMemos(copy);
}
}

View File

@ -0,0 +1,43 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigException;
// value is allowed to be null
final class ResolveResult<V extends AbstractConfigValue> {
public final ResolveContext context;
public final V value;
private ResolveResult(ResolveContext context, V value) {
this.context = context;
this.value = value;
}
static <V extends AbstractConfigValue> ResolveResult<V> make(ResolveContext context, V value) {
return new ResolveResult<V>(context, value);
}
// better option? we don't have variance
@SuppressWarnings("unchecked")
ResolveResult<AbstractConfigObject> asObjectResult() {
if (!(value instanceof AbstractConfigObject))
throw new ConfigException.BugOrBroken("Expecting a resolve result to be an object, but it was " + value);
Object o = this;
return (ResolveResult<AbstractConfigObject>) o;
}
// better option? we don't have variance
@SuppressWarnings("unchecked")
ResolveResult<AbstractConfigValue> asValueResult() {
Object o = this;
return (ResolveResult<AbstractConfigValue>) o;
}
ResolveResult<V> popTrace() {
return make(context.popTrace(), value);
}
@Override
public String toString() {
return "ResolveResult(" + value + ")";
}
}

View File

@ -41,14 +41,18 @@ final class ResolveSource {
// child being peeked, but NOT the child itself. Caller has to resolve
// the child itself if needed. ValueWithPath.value can be null but
// the ValueWithPath instance itself should not be.
static private ValueWithPath findInObject(AbstractConfigObject obj, ResolveContext context, Path path)
static private ResultWithPath findInObject(AbstractConfigObject obj, ResolveContext context, Path path)
throws NotPossibleToResolve {
// resolve ONLY portions of the object which are along our path
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace("*** finding '" + path + "' in " + obj);
AbstractConfigValue partiallyResolved = context.restrict(path).resolve(obj, new ResolveSource(obj));
if (partiallyResolved instanceof AbstractConfigObject) {
return findInObject((AbstractConfigObject) partiallyResolved, path);
Path restriction = context.restrictToChild();
ResolveResult<? extends AbstractConfigValue> partiallyResolved = context.restrict(path).resolve(obj,
new ResolveSource(obj));
ResolveContext newContext = partiallyResolved.context.restrict(restriction);
if (partiallyResolved.value instanceof AbstractConfigObject) {
ValueWithPath pair = findInObject((AbstractConfigObject) partiallyResolved.value, path);
return new ResultWithPath(ResolveResult.make(newContext, pair.value), pair.pathFromRoot);
} else {
throw new ConfigException.BugOrBroken("resolved object to non-object " + obj + " to " + partiallyResolved);
}
@ -83,7 +87,7 @@ final class ResolveSource {
}
}
ValueWithPath lookupSubst(ResolveContext context, SubstitutionExpression subst,
ResultWithPath lookupSubst(ResolveContext context, SubstitutionExpression subst,
int prefixLength)
throws NotPossibleToResolve {
if (ConfigImpl.traceSubstitutionsEnabled())
@ -93,12 +97,9 @@ final class ResolveSource {
ConfigImpl.trace(context.depth(), subst + " - looking up relative to file it occurred in");
// First we look up the full path, which means relative to the
// included file if we were not a root file
ValueWithPath result = findInObject(root, context, subst.path());
ResultWithPath result = findInObject(root, context, subst.path());
if (result == null)
throw new ConfigException.BugOrBroken("findInObject() returned null");
if (result.value == null) {
if (result.result.value == null) {
// Then we want to check relative to the root file. We don't
// want the prefix we were included at to be used when looking
// up env variables either.
@ -106,19 +107,20 @@ final class ResolveSource {
if (prefixLength > 0) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), unprefixed + " - looking up relative to parent file");
result = findInObject(root, context, unprefixed);
ConfigImpl.trace(result.result.context.depth(), unprefixed
+ " - looking up relative to parent file");
result = findInObject(root, result.result.context, unprefixed);
}
if (result.value == null && context.options().getUseSystemEnvironment()) {
if (result.result.value == null && result.result.context.options().getUseSystemEnvironment()) {
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), unprefixed + " - looking up in system environment");
ConfigImpl.trace(result.result.context.depth(), unprefixed + " - looking up in system environment");
result = findInObject(ConfigImpl.envVariablesAsConfigObject(), context, unprefixed);
}
}
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace(context.depth(), "resolved to " + result);
ConfigImpl.trace(result.result.context.depth(), "resolved to " + result);
return result;
}
@ -391,4 +393,30 @@ final class ResolveSource {
return "ValueWithPath(value=" + value + ", pathFromRoot=" + pathFromRoot + ")";
}
}
static final class ResultWithPath {
final ResolveResult<? extends AbstractConfigValue> result;
final Node<Container> pathFromRoot;
ResultWithPath(ResolveResult<? extends AbstractConfigValue> result, Node<Container> pathFromRoot) {
this.result = result;
this.pathFromRoot = pathFromRoot;
}
ResultWithPath(ResolveResult<? extends AbstractConfigValue> result) {
this(result, null);
}
ResultWithPath addParent(Container parent) {
if (pathFromRoot == null)
return new ResultWithPath(result, new Node<Container>(parent));
else
return new ResultWithPath(result, pathFromRoot.prepend(parent));
}
@Override
public String toString() {
return "ResultWithPath(result=" + result + ", pathFromRoot=" + pathFromRoot + ")";
}
}
}

View File

@ -121,27 +121,38 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
}
}
private static class ResolveModifier implements Modifier {
ResolveContext context;
final ResolveSource source;
ResolveModifier(ResolveContext context, ResolveSource source) {
this.context = context;
this.source = source;
}
@Override
public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v)
throws NotPossibleToResolve {
ResolveResult<? extends AbstractConfigValue> result = context.resolve(v, source);
context = result.context;
return result.value;
}
}
@Override
SimpleConfigList resolveSubstitutions(final ResolveContext context, ResolveSource source)
ResolveResult<? extends SimpleConfigList> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
if (resolved)
return this;
return ResolveResult.make(context, this);
if (context.isRestrictedToChild()) {
// if a list restricts to a child path, then it has no child paths,
// so nothing to do.
return this;
return ResolveResult.make(context, this);
} else {
final ResolveSource sourceWithParent = source.pushParent(this);
try {
return modifyMayThrow(new Modifier() {
@Override
public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v)
throws NotPossibleToResolve {
return context.resolve(v, sourceWithParent);
}
}, ResolveStatus.RESOLVED);
ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this));
SimpleConfigList value = modifyMayThrow(modifier, ResolveStatus.RESOLVED);
return ResolveResult.make(modifier.context, value);
} catch (NotPossibleToResolve e) {
throw e;
} catch (RuntimeException e) {

View File

@ -344,41 +344,59 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
}
}
private static final class ResolveModifier implements Modifier {
final Path originalRestrict;
ResolveContext context;
final ResolveSource source;
ResolveModifier(ResolveContext context, ResolveSource source) {
this.context = context;
this.source = source;
originalRestrict = context.restrictToChild();
}
@Override
public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve {
if (context.isRestrictedToChild()) {
if (key.equals(context.restrictToChild().first())) {
Path remainder = context.restrictToChild().remainder();
if (remainder != null) {
ResolveResult<? extends AbstractConfigValue> result = context.restrict(remainder).resolve(v,
source);
context = result.context.unrestricted().restrict(originalRestrict);
return result.value;
} else {
// we don't want to resolve the leaf child.
return v;
}
} else {
// not in the restrictToChild path
return v;
}
} else {
// no restrictToChild, resolve everything
ResolveResult<? extends AbstractConfigValue> result = context.unrestricted().resolve(v, source);
context = result.context.unrestricted().restrict(originalRestrict);
return result.value;
}
}
}
@Override
AbstractConfigObject resolveSubstitutions(final ResolveContext context, ResolveSource source)
ResolveResult<? extends AbstractConfigObject> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
if (resolveStatus() == ResolveStatus.RESOLVED)
return this;
return ResolveResult.make(context, this);
final ResolveSource sourceWithParent = source.pushParent(this);
try {
return modifyMayThrow(new Modifier() {
ResolveModifier modifier = new ResolveModifier(context, sourceWithParent);
@Override
public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v)
throws NotPossibleToResolve {
if (context.isRestrictedToChild()) {
if (key.equals(context.restrictToChild().first())) {
Path remainder = context.restrictToChild().remainder();
if (remainder != null) {
return context.restrict(remainder).resolve(v, sourceWithParent);
} else {
// we don't want to resolve the leaf child.
return v;
}
} else {
// not in the restrictToChild path
return v;
}
} else {
// no restrictToChild, resolve everything
return context.unrestricted().resolve(v, sourceWithParent);
}
}
});
AbstractConfigValue value = modifyMayThrow(modifier);
return ResolveResult.make(modifier.context, value).asObjectResult();
} catch (NotPossibleToResolve e) {
throw e;
} catch (RuntimeException e) {