move the memoizing functionality out of SubstitutionResolver

This commit is contained in:
Havoc Pennington 2012-03-30 20:10:05 -04:00
parent d6b27c839a
commit 0101d9ef37
8 changed files with 122 additions and 98 deletions

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
});
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}