package org.deephacks.confit.internal.core.property.typesafe.impl;
import java.util.List;
import java.util.ArrayList;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException.BugOrBroken;
import org.deephacks.confit.internal.core.property.typesafe.ConfigResolveOptions;
import org.deephacks.confit.internal.core.property.typesafe.impl.AbstractConfigValue.NotPossibleToResolve;
final class ResolveContext {
// this is unfortunately mutable so should only be shared among
// ResolveContext in the same traversal.
final private ResolveSource source;
// this is unfortunately mutable so should only be shared among
// ResolveContext in the same traversal.
final private ResolveMemos memos;
final private ConfigResolveOptions options;
// the current path restriction, used to ensure lazy
// resolution and avoid gratuitous cycles. without this,
// any sibling of an object we're traversing could
// cause a cycle "by side effect"
// CAN BE NULL for a full resolve.
final private Path restrictToChild;
// another mutable unfortunate. This is
// used to make nice error messages when
// resolution fails.
final private List<SubstitutionExpression> expressionTrace;
ResolveContext(ResolveSource source, ResolveMemos memos, ConfigResolveOptions options,
Path restrictToChild, List<SubstitutionExpression> expressionTrace) {
this.source = source;
this.memos = memos;
this.options = options;
this.restrictToChild = restrictToChild;
this.expressionTrace = expressionTrace;
}
ResolveContext(AbstractConfigObject root, ConfigResolveOptions options, Path restrictToChild) {
// LinkedHashSet keeps the traversal order which is at least useful
// in error messages if nothing else
this(new ResolveSource(root), new ResolveMemos(), options, restrictToChild,
new ArrayList<SubstitutionExpression>());
}
ResolveSource source() {
return source;
}
ConfigResolveOptions options() {
return options;
}
boolean isRestrictedToChild() {
return restrictToChild != null;
}
Path restrictToChild() {
return restrictToChild;
}
ResolveContext restrict(Path restrictTo) {
if (restrictTo == restrictToChild)
return this;
else
return new ResolveContext(source, memos, options, restrictTo, expressionTrace);
}
ResolveContext unrestricted() {
return restrict(null);
}
void trace(SubstitutionExpression expr) {
expressionTrace.add(expr);
}
void untrace() {
expressionTrace.remove(expressionTrace.size() - 1);
}
String traceString() {
String separator = ", ";
StringBuilder sb = new StringBuilder();
for (SubstitutionExpression expr : expressionTrace) {
sb.append(expr.toString());
sb.append(separator);
}
if (sb.length() > 0)
sb.setLength(sb.length() - separator.length());
return sb.toString();
}
AbstractConfigValue resolve(AbstractConfigValue original) throws NotPossibleToResolve {
// a fully-resolved (no restrictToChild) object can satisfy a
// request for a restricted object, so always check that first.
final MemoKey fullKey = new MemoKey(original, null);
MemoKey restrictedKey = null;
AbstractConfigValue cached = memos.get(fullKey);
// but if there was no fully-resolved object cached, we'll only
// compute the restrictToChild object so use a more limited
// memo key
if (cached == null && isRestrictedToChild()) {
restrictedKey = new MemoKey(original, restrictToChild());
cached = memos.get(restrictedKey);
}
if (cached != null) {
return cached;
} else {
AbstractConfigValue resolved = source.resolveCheckingReplacement(this, original);
if (resolved == null || resolved.resolveStatus() == ResolveStatus.RESOLVED) {
// if the resolved object is fully resolved by resolving
// only the restrictToChildOrNull, then it can be cached
// under fullKey since the child we were restricted to
// turned out to be the only unresolved thing.
memos.put(fullKey, resolved);
} else {
// if we have an unresolved object then either we did a
// partial resolve restricted to a certain child, or it's
// a bug.
if (isRestrictedToChild()) {
if (restrictedKey == null) {
throw new BugOrBroken(
"restrictedKey should not be null here");
}
memos.put(restrictedKey, resolved);
} else {
throw new BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
}
}
return resolved;
}
}
static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root,
ConfigResolveOptions options) {
ResolveContext context = new ResolveContext(root, options, null /* restrictToChild */);
try {
return context.resolve(value);
} catch (NotPossibleToResolve e) {
// ConfigReference was supposed to catch NotPossibleToResolve
throw new BugOrBroken(
"NotPossibleToResolve was thrown from an outermost resolve", e);
}
}
}