mirror of
https://github.com/lightbend/config.git
synced 2025-01-29 05:30:08 +08:00
move the memoizing functionality out of SubstitutionResolver
This commit is contained in:
parent
d6b27c839a
commit
0101d9ef37
@ -113,8 +113,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi
|
||||
// walk down through the path resolving only things along that
|
||||
// path,
|
||||
// and then recursively call ourselves with no resolver.
|
||||
AbstractConfigValue partiallyResolved = resolver.resolve(self,
|
||||
context.restrict(path));
|
||||
AbstractConfigValue partiallyResolved = context.restrict(path).resolve(resolver,
|
||||
self);
|
||||
if (partiallyResolved instanceof AbstractConfigObject) {
|
||||
return peekPath((AbstractConfigObject) partiallyResolved, path, null, null);
|
||||
} else {
|
||||
|
@ -99,7 +99,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
|
||||
|
||||
AbstractConfigValue resolved;
|
||||
try {
|
||||
resolved = resolver.resolve(v, context);
|
||||
resolved = context.resolve(resolver, v);
|
||||
} finally {
|
||||
if (replaced)
|
||||
context.unreplace((AbstractConfigValue) replaceable);
|
||||
|
@ -18,6 +18,11 @@ 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;
|
||||
|
||||
// Resolves that we have already begun (for cycle detection).
|
||||
// SubstitutionResolver separately memoizes completed resolves.
|
||||
// this set is unfortunately mutable and the user of ResolveContext
|
||||
@ -33,10 +38,11 @@ final class ResolveContext {
|
||||
// given replacement instead.
|
||||
final private Map<MemoKey, LinkedList<ResolveReplacer>> replacements;
|
||||
|
||||
ResolveContext(ResolveSource source, LinkedList<Set<MemoKey>> traversedStack,
|
||||
ConfigResolveOptions options, Path restrictToChild,
|
||||
Map<MemoKey, LinkedList<ResolveReplacer>> replacements) {
|
||||
ResolveContext(ResolveSource source, ResolveMemos memos,
|
||||
LinkedList<Set<MemoKey>> traversedStack, ConfigResolveOptions options,
|
||||
Path restrictToChild, Map<MemoKey, LinkedList<ResolveReplacer>> replacements) {
|
||||
this.source = source;
|
||||
this.memos = memos;
|
||||
this.traversedStack = traversedStack;
|
||||
this.options = options;
|
||||
this.restrictToChild = restrictToChild;
|
||||
@ -46,9 +52,9 @@ final class ResolveContext {
|
||||
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 LinkedList<Set<MemoKey>>(
|
||||
Collections.singletonList(new LinkedHashSet<MemoKey>())),
|
||||
options, restrictToChild, new HashMap<MemoKey, LinkedList<ResolveReplacer>>());
|
||||
this(new ResolveSource(root), new ResolveMemos(), new LinkedList<Set<MemoKey>>(
|
||||
Collections.singletonList(new LinkedHashSet<MemoKey>())), options, restrictToChild,
|
||||
new HashMap<MemoKey, LinkedList<ResolveReplacer>>());
|
||||
}
|
||||
|
||||
private void traverse(ConfigSubstitution value, SubstitutionExpression via)
|
||||
@ -150,10 +156,81 @@ final class ResolveContext {
|
||||
if (restrictTo == restrictToChild)
|
||||
return this;
|
||||
else
|
||||
return new ResolveContext(source, traversedStack, options, restrictTo, replacements);
|
||||
return new ResolveContext(source, memos, traversedStack, options, restrictTo,
|
||||
replacements);
|
||||
}
|
||||
|
||||
ResolveContext unrestricted() {
|
||||
return restrict(null);
|
||||
}
|
||||
|
||||
AbstractConfigValue resolve(SubstitutionResolver resolver, 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 {
|
||||
MemoKey key = restrictedKey != null ? restrictedKey : fullKey;
|
||||
|
||||
AbstractConfigValue replacement;
|
||||
boolean forceUndefined = false;
|
||||
try {
|
||||
replacement = replacement(key);
|
||||
} catch (Undefined e) {
|
||||
replacement = original;
|
||||
forceUndefined = true;
|
||||
}
|
||||
|
||||
if (replacement != original) {
|
||||
// start over, checking if replacement was memoized
|
||||
return resolve(resolver, replacement);
|
||||
} else {
|
||||
AbstractConfigValue resolved;
|
||||
|
||||
if (forceUndefined)
|
||||
resolved = null;
|
||||
else
|
||||
resolved = original.resolveSubstitutions(resolver, this);
|
||||
|
||||
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 ConfigException.BugOrBroken(
|
||||
"restrictedKey should not be null here");
|
||||
}
|
||||
memos.put(restrictedKey, resolved);
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"resolveSubstitutions() did not give us a resolved object");
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This exists because we have to memoize resolved substitutions as we go
|
||||
* through the config tree; otherwise we could end up creating multiple copies
|
||||
* of values or whole trees of values as we follow chains of substitutions.
|
||||
*/
|
||||
final class ResolveMemos {
|
||||
// note that we can resolve things to undefined (represented as Java null,
|
||||
// rather than ConfigNull) so this map can have null values.
|
||||
final private Map<MemoKey, AbstractConfigValue> memos;
|
||||
|
||||
ResolveMemos() {
|
||||
this.memos = new HashMap<MemoKey, AbstractConfigValue>();
|
||||
}
|
||||
|
||||
AbstractConfigValue get(MemoKey key) {
|
||||
return memos.get(key);
|
||||
}
|
||||
|
||||
void put(MemoKey key, AbstractConfigValue value) {
|
||||
memos.put(key, value);
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ final class ResolveSource {
|
||||
result = context.traversing(traversed, subst, new ResolveContext.Resolver() {
|
||||
@Override
|
||||
public AbstractConfigValue call() throws NotPossibleToResolve {
|
||||
return resolver.resolve(unresolved, context);
|
||||
return context.resolve(resolver, unresolved);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
|
||||
@Override
|
||||
public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v)
|
||||
throws NotPossibleToResolve {
|
||||
return resolver.resolve(v, context);
|
||||
return context.resolve(resolver, v);
|
||||
}
|
||||
|
||||
}, ResolveStatus.RESOLVED);
|
||||
|
@ -274,7 +274,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
|
||||
if (key.equals(context.restrictToChild().first())) {
|
||||
Path remainder = context.restrictToChild().remainder();
|
||||
if (remainder != null) {
|
||||
return resolver.resolve(v, context.restrict(remainder));
|
||||
return context.restrict(remainder).resolve(resolver, v);
|
||||
} else {
|
||||
// we don't want to resolve the leaf child.
|
||||
return v;
|
||||
@ -285,7 +285,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
|
||||
}
|
||||
} else {
|
||||
// no restrictToChild, resolve everything
|
||||
return resolver.resolve(v, context.unrestricted());
|
||||
return context.unrestricted().resolve(resolver, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,96 +3,16 @@
|
||||
*/
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigResolveOptions;
|
||||
import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve;
|
||||
import com.typesafe.config.impl.ResolveReplacer.Undefined;
|
||||
|
||||
/**
|
||||
* This exists because we have to memoize resolved substitutions as we go
|
||||
* through the config tree; otherwise we could end up creating multiple copies
|
||||
* of values or whole trees of values as we follow chains of substitutions.
|
||||
* TODO there is no reason for this class to exist, will remove in upcoming
|
||||
* commit
|
||||
*/
|
||||
final class SubstitutionResolver {
|
||||
// note that we can resolve things to undefined (represented as Java null,
|
||||
// rather than ConfigNull) so this map can have null values.
|
||||
final private Map<MemoKey, AbstractConfigValue> memos;
|
||||
|
||||
SubstitutionResolver() {
|
||||
this.memos = new HashMap<MemoKey, AbstractConfigValue>();
|
||||
}
|
||||
|
||||
AbstractConfigValue resolve(AbstractConfigValue original, ResolveContext context)
|
||||
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 && context.isRestrictedToChild()) {
|
||||
restrictedKey = new MemoKey(original, context.restrictToChild());
|
||||
cached = memos.get(restrictedKey);
|
||||
}
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
} else {
|
||||
MemoKey key = restrictedKey != null ? restrictedKey : fullKey;
|
||||
|
||||
AbstractConfigValue replacement;
|
||||
boolean forceUndefined = false;
|
||||
try {
|
||||
replacement = context.replacement(key);
|
||||
} catch (Undefined e) {
|
||||
replacement = original;
|
||||
forceUndefined = true;
|
||||
}
|
||||
|
||||
if (replacement != original) {
|
||||
// start over, checking if replacement was memoized
|
||||
return resolve(replacement, context);
|
||||
} else {
|
||||
AbstractConfigValue resolved;
|
||||
|
||||
if (forceUndefined)
|
||||
resolved = null;
|
||||
else
|
||||
resolved = original.resolveSubstitutions(this, 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
|
||||
// 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 (context.isRestrictedToChild()) {
|
||||
if (restrictedKey == null) {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"restrictedKey should not be null here");
|
||||
}
|
||||
memos.put(restrictedKey, resolved);
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"resolveSubstitutions() did not give us a resolved object");
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root,
|
||||
@ -100,7 +20,7 @@ final class SubstitutionResolver {
|
||||
SubstitutionResolver resolver = new SubstitutionResolver();
|
||||
ResolveContext context = new ResolveContext(root, options, restrictToChildOrNull);
|
||||
|
||||
return resolver.resolve(value, context);
|
||||
return context.resolve(resolver, value);
|
||||
}
|
||||
|
||||
static AbstractConfigValue resolveWithExternalExceptions(AbstractConfigValue value,
|
||||
@ -109,7 +29,7 @@ final class SubstitutionResolver {
|
||||
ResolveContext context = new ResolveContext(root, options, null /* restrictToChild */);
|
||||
|
||||
try {
|
||||
return resolver.resolve(value, context);
|
||||
return context.resolve(resolver, value);
|
||||
} catch (NotPossibleToResolve e) {
|
||||
throw e.exportException(value.origin(), null);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user