diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java index a1480d24..1765dddb 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java @@ -1,9 +1,7 @@ package com.typesafe.config.impl; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; -import java.util.Map; import java.util.Set; import java.util.LinkedHashSet; import java.util.concurrent.Callable; @@ -36,27 +34,22 @@ final class ResolveContext { // cause a cycle "by side effect" // CAN BE NULL for a full resolve. final private Path restrictToChild; - // if we try to resolve something in here, use the - // given replacement instead. - final private Map> replacements; ResolveContext(ResolveSource source, ResolveMemos memos, LinkedList> traversedStack, ConfigResolveOptions options, - Path restrictToChild, Map> replacements) { + Path restrictToChild) { this.source = source; this.memos = memos; this.traversedStack = traversedStack; this.options = options; this.restrictToChild = restrictToChild; - this.replacements = replacements; } 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(), new LinkedList>( - Collections.singletonList(new LinkedHashSet())), options, restrictToChild, - new HashMap>()); + Collections.singletonList(new LinkedHashSet())), options, restrictToChild); } private void traverse(ConfigSubstitution value, SubstitutionExpression via) @@ -105,39 +98,22 @@ final class ResolveContext { } void replace(AbstractConfigValue value, ResolveReplacer replacer) { - MemoKey key = new MemoKey(value, null /* restrictToChild */); - LinkedList stack = replacements.get(key); - if (stack == null) { - stack = new LinkedList(); - replacements.put(key, stack); - } - stack.addFirst(replacer); + source.replace(value, replacer); + // we have to reset the cycle detection because with the - // replacement, a cycle may be broken + // replacement, a cycle may not exist anymore. traversedStack.addFirst(new LinkedHashSet()); } void unreplace(AbstractConfigValue value) { - MemoKey key = new MemoKey(value, null /* restrictToChild */); - LinkedList stack = replacements.get(key); - if (stack == null) - throw new ConfigException.BugOrBroken("unreplace() without replace(): " + value); + source.unreplace(value); - stack.removeFirst(); Set oldTraversed = traversedStack.removeFirst(); if (!oldTraversed.isEmpty()) throw new ConfigException.BugOrBroken( "unreplace() with stuff still in the traverse set: " + oldTraversed); } - AbstractConfigValue replacement(MemoKey key) throws Undefined { - LinkedList stack = replacements.get(new MemoKey(key.value(), null)); - if (stack == null || stack.isEmpty()) - return key.value(); - else - return stack.peek().replace(); - } - ResolveSource source() { return source; } @@ -158,8 +134,7 @@ final class ResolveContext { if (restrictTo == restrictToChild) return this; else - return new ResolveContext(source, memos, traversedStack, options, restrictTo, - replacements); + return new ResolveContext(source, memos, traversedStack, options, restrictTo); } ResolveContext unrestricted() { @@ -192,7 +167,7 @@ final class ResolveContext { AbstractConfigValue replacement; boolean forceUndefined = false; try { - replacement = replacement(key); + replacement = source.replacement(key); } catch (Undefined e) { replacement = original; forceUndefined = true; diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveSource.java b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java index 198742c1..bdb8d96b 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveSource.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java @@ -1,15 +1,28 @@ package com.typesafe.config.impl; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.typesafe.config.ConfigException; import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; +import com.typesafe.config.impl.ResolveReplacer.Undefined; /** * This class is the source for values for a substitution like ${foo}. */ final class ResolveSource { final private AbstractConfigObject root; + // Conceptually, we transform the ResolveSource whenever we traverse + // a substitution or delayed merge stack, in order to remove the + // traversed node and therefore avoid circular dependencies. + // We implement it with this somewhat hacky "patch a replacement" + // mechanism instead of actually transforming the tree. + final private Map> replacements; ResolveSource(AbstractConfigObject root) { this.root = root; + this.replacements = new HashMap>(); } static private AbstractConfigValue findInObject(final AbstractConfigObject obj, @@ -58,4 +71,31 @@ final class ResolveSource { return result; } + + void replace(AbstractConfigValue value, ResolveReplacer replacer) { + MemoKey key = new MemoKey(value, null /* restrictToChild */); + LinkedList stack = replacements.get(key); + if (stack == null) { + stack = new LinkedList(); + replacements.put(key, stack); + } + stack.addFirst(replacer); + } + + void unreplace(AbstractConfigValue value) { + MemoKey key = new MemoKey(value, null /* restrictToChild */); + LinkedList stack = replacements.get(key); + if (stack == null) + throw new ConfigException.BugOrBroken("unreplace() without replace(): " + value); + + stack.removeFirst(); + } + + AbstractConfigValue replacement(MemoKey key) throws Undefined { + LinkedList stack = replacements.get(new MemoKey(key.value(), null)); + if (stack == null || stack.isEmpty()) + return key.value(); + else + return stack.peek().replace(); + } }