diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java index 440caf6d..7d4f68ec 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java @@ -133,54 +133,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements return pieces; } - /** resolver is null if we should not have refs */ - private AbstractConfigValue findInObject(final AbstractConfigObject root, - final SubstitutionResolver resolver, final SubstitutionExpression subst, - final ResolveContext context) throws NotPossibleToResolve { - return context.traversing(this, subst, new ResolveContext.Resolver() { - @Override - public AbstractConfigValue call() throws NotPossibleToResolve { - return root.peekPath(subst.path(), resolver, context); - } - }); - } - private AbstractConfigValue resolve(final SubstitutionResolver resolver, - final SubstitutionExpression subst, final ResolveContext context) - throws NotPossibleToResolve { - // First we look up the full path, which means relative to the - // included file if we were not a root file - AbstractConfigValue result = findInObject(resolver.root(), resolver, subst, context); - - if (result == 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. - SubstitutionExpression unprefixed = subst - .changePath(subst.path().subPath(prefixLength)); - - if (result == null && prefixLength > 0) { - result = findInObject(resolver.root(), resolver, unprefixed, context); - } - - if (result == null && context.options().getUseSystemEnvironment()) { - result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, unprefixed, - context); - } - } - - if (result != null) { - final AbstractConfigValue unresolved = result; - result = context.traversing(this, subst, new ResolveContext.Resolver() { - @Override - public AbstractConfigValue call() throws NotPossibleToResolve { - return resolver.resolve(unresolved, context); - } - }); - } - - return result; - } private static ResolveReplacer undefinedReplacer = new ResolveReplacer() { @Override @@ -201,7 +154,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements // to concat into a string we have to do a full resolve, // so unrestrict the context - AbstractConfigValue v = resolve(resolver, exp, context.unrestricted()); + AbstractConfigValue v = context.source().lookupSubst(resolver, + context.unrestricted(), this, exp, prefixLength); if (v == null) { if (exp.optional()) { @@ -233,7 +187,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements "ConfigSubstitution should never contain a single String piece"); SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0); - AbstractConfigValue v = resolve(resolver, exp, context); + AbstractConfigValue v = context.source().lookupSubst(resolver, context, this, exp, + prefixLength); if (v == null && !exp.optional()) { throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString()); 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 bb17fc5e..05e65b9e 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java @@ -15,6 +15,9 @@ import com.typesafe.config.impl.AbstractConfigValue.SelfReferential; import com.typesafe.config.impl.ResolveReplacer.Undefined; final class ResolveContext { + // this is unfortunately mutable so should only be shared among + // ResolveContext in the same traversal. + final private ResolveSource source; // Resolves that we have already begun (for cycle detection). // SubstitutionResolver separately memoizes completed resolves. // this set is unfortunately mutable and the user of ResolveContext @@ -30,19 +33,21 @@ final class ResolveContext { // given replacement instead. final private Map<MemoKey, LinkedList<ResolveReplacer>> replacements; - ResolveContext(LinkedList<Set<MemoKey>> traversedStack, ConfigResolveOptions options, - Path restrictToChild, + ResolveContext(ResolveSource source, LinkedList<Set<MemoKey>> traversedStack, + ConfigResolveOptions options, Path restrictToChild, Map<MemoKey, LinkedList<ResolveReplacer>> replacements) { + this.source = source; this.traversedStack = traversedStack; this.options = options; this.restrictToChild = restrictToChild; this.replacements = replacements; } - ResolveContext(ConfigResolveOptions options, Path restrictToChild) { + 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 LinkedList<Set<MemoKey>>(Collections.singletonList(new LinkedHashSet<MemoKey>())), + this(new ResolveSource(root), new LinkedList<Set<MemoKey>>( + Collections.singletonList(new LinkedHashSet<MemoKey>())), options, restrictToChild, new HashMap<MemoKey, LinkedList<ResolveReplacer>>()); } @@ -125,6 +130,10 @@ final class ResolveContext { return stack.peek().replace(); } + ResolveSource source() { + return source; + } + ConfigResolveOptions options() { return options; } @@ -141,7 +150,7 @@ final class ResolveContext { if (restrictTo == restrictToChild) return this; else - return new ResolveContext(traversedStack, options, restrictTo, replacements); + return new ResolveContext(source, traversedStack, options, restrictTo, replacements); } ResolveContext unrestricted() { diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveSource.java b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java new file mode 100644 index 00000000..bab97a43 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java @@ -0,0 +1,64 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; + +/** + * This class is the source for values for a substitution like ${foo}. + */ +final class ResolveSource { + final private AbstractConfigObject root; + + ResolveSource(AbstractConfigObject root) { + this.root = root; + } + + /** resolver is null if we should not have refs */ + static private AbstractConfigValue findInObject(final AbstractConfigObject obj, + final SubstitutionResolver resolver, final ResolveContext context, + ConfigSubstitution traversed, final SubstitutionExpression subst) + throws NotPossibleToResolve { + return context.traversing(traversed, subst, new ResolveContext.Resolver() { + @Override + public AbstractConfigValue call() throws NotPossibleToResolve { + return obj.peekPath(subst.path(), resolver, context); + } + }); + } + + AbstractConfigValue lookupSubst(final SubstitutionResolver resolver, + final ResolveContext context, ConfigSubstitution traversed, + final SubstitutionExpression subst, int prefixLength) throws NotPossibleToResolve { + // First we look up the full path, which means relative to the + // included file if we were not a root file + AbstractConfigValue result = findInObject(root, resolver, context, traversed, subst); + + if (result == 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. + SubstitutionExpression unprefixed = subst + .changePath(subst.path().subPath(prefixLength)); + + if (result == null && prefixLength > 0) { + result = findInObject(root, resolver, context, traversed, unprefixed); + } + + if (result == null && context.options().getUseSystemEnvironment()) { + result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, context, + traversed, unprefixed); + } + } + + if (result != null) { + final AbstractConfigValue unresolved = result; + result = context.traversing(traversed, subst, new ResolveContext.Resolver() { + @Override + public AbstractConfigValue call() throws NotPossibleToResolve { + return resolver.resolve(unresolved, context); + } + }); + } + + return result; + } +} diff --git a/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java b/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java index d2e2a701..10f73a21 100644 --- a/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java +++ b/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java @@ -17,13 +17,11 @@ import com.typesafe.config.impl.ResolveReplacer.Undefined; * of values or whole trees of values as we follow chains of substitutions. */ final class SubstitutionResolver { - final private AbstractConfigObject root; // 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(AbstractConfigObject root) { - this.root = root; + SubstitutionResolver() { this.memos = new HashMap<MemoKey, AbstractConfigValue>(); } @@ -97,22 +95,18 @@ final class SubstitutionResolver { } } - AbstractConfigObject root() { - return this.root; - } - static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root, ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve { - SubstitutionResolver resolver = new SubstitutionResolver(root); - ResolveContext context = new ResolveContext(options, restrictToChildOrNull); + SubstitutionResolver resolver = new SubstitutionResolver(); + ResolveContext context = new ResolveContext(root, options, restrictToChildOrNull); return resolver.resolve(value, context); } static AbstractConfigValue resolveWithExternalExceptions(AbstractConfigValue value, AbstractConfigObject root, ConfigResolveOptions options) { - SubstitutionResolver resolver = new SubstitutionResolver(root); - ResolveContext context = new ResolveContext(options, null /* restrictToChild */); + SubstitutionResolver resolver = new SubstitutionResolver(); + ResolveContext context = new ResolveContext(root, options, null /* restrictToChild */); try { return resolver.resolve(value, context);