mirror of
https://github.com/lightbend/config.git
synced 2025-01-15 23:01:05 +08:00
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:
parent
afdcbb3803
commit
c8f42ef92d
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 + ")";
|
||||
}
|
||||
}
|
@ -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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user