diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index b90f7845..121df218 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -6,16 +6,13 @@ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -92,9 +89,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements * @throws NotPossibleToResolve */ protected AbstractConfigValue peekPath(Path path, SubstitutionResolver resolver, - Set traversed, ConfigResolveOptions options) - throws NotPossibleToResolve, NeedsFullResolve { - return peekPath(this, path, resolver, traversed, options); + ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { + return peekPath(this, path, resolver, context); } /** @@ -103,7 +99,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements */ AbstractConfigValue peekPathWithExternalExceptions(Path path) { try { - return peekPath(this, path, null, Collections. emptySet(), null); + return peekPath(this, path, null, null); } catch (NotPossibleToResolve e) { throw e.exportException(origin(), path.render()); } catch (NeedsFullResolve e) { @@ -116,16 +112,14 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements // child being peeked, but NOT the child itself. Caller has to resolve // the child itself if needed. private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path, - SubstitutionResolver resolver, Set traversed, ConfigResolveOptions options) + SubstitutionResolver resolver, ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { if (resolver != null) { // walk down through the path resolving only things along that path, // and then recursively call ourselves with no resolver. - AbstractConfigValue partiallyResolved = resolver - .resolve(self, traversed, options, path); + AbstractConfigValue partiallyResolved = resolver.resolve(self, context.restrict(path)); if (partiallyResolved instanceof AbstractConfigObject) { - return peekPath((AbstractConfigObject) partiallyResolved, path, null, - traversed, null); + return peekPath((AbstractConfigObject) partiallyResolved, path, null, null); } else { throw new ConfigException.BugOrBroken("resolved object to non-object " + self + " to " + partiallyResolved); @@ -140,8 +134,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements return v; } else { if (v instanceof AbstractConfigObject) { - return peekPath((AbstractConfigObject) v, next, null, - Collections. emptySet(), null); + return peekPath((AbstractConfigObject) v, next, null, null); } else { return null; } @@ -223,9 +216,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements } @Override - abstract AbstractConfigObject resolveSubstitutions(final SubstitutionResolver resolver, - Set traversed, ConfigResolveOptions options, Path restrictToChildOrNull) - throws NotPossibleToResolve, NeedsFullResolve; + abstract AbstractConfigObject resolveSubstitutions(SubstitutionResolver resolver, + ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve; @Override abstract AbstractConfigObject relativized(final Path prefix); diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index 17412a7c..fd8ada85 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -4,12 +4,10 @@ package com.typesafe.config.impl; import java.io.Serializable; -import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; /** @@ -102,17 +100,12 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue, Seria * * @param resolver * the resolver doing the resolving - * @param traversed - * objects which have already been visited, will include this one - * @param options - * whether to look at system props and env vars - * @param restrictToChildOrNull - * if non-null, only recurse into this child path + * @param context + * state of the current resolve * @return a new value if there were changes, or this if no changes */ - AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, Set traversed, - ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve, - NeedsFullResolve { + AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, ResolveContext context) + throws NotPossibleToResolve, NeedsFullResolve { return this; } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java index 72d0eeb0..16b68ad0 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -8,11 +8,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValueType; /** @@ -65,16 +63,15 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements } @Override - AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, Set traversed, - ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve, - NeedsFullResolve { - return resolveSubstitutions(stack, resolver, traversed, options, restrictToChildOrNull); + AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, ResolveContext context) + throws NotPossibleToResolve, NeedsFullResolve { + return resolveSubstitutions(stack, resolver, context); } // static method also used by ConfigDelayedMergeObject static AbstractConfigValue resolveSubstitutions(List stack, - SubstitutionResolver resolver, Set traversed, ConfigResolveOptions options, - Path restrictToChildOrNull) throws NotPossibleToResolve, NeedsFullResolve { + SubstitutionResolver resolver, ResolveContext context) throws NotPossibleToResolve, + NeedsFullResolve { // to resolve substitutions, we need to recursively resolve // the stack of stuff to merge, and merge the stack so // we won't be a delayed merge anymore. If restrictToChildOrNull @@ -82,8 +79,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements AbstractConfigValue merged = null; for (AbstractConfigValue v : stack) { - AbstractConfigValue resolved = resolver.resolve(v, traversed, options, - restrictToChildOrNull); + AbstractConfigValue resolved = resolver.resolve(v, context); if (resolved != null) { if (merged == null) merged = resolved; diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java index cde3f999..30445822 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -13,7 +13,6 @@ import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; // This is just like ConfigDelayedMerge except we know statically @@ -61,11 +60,10 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements } @Override - AbstractConfigObject resolveSubstitutions(SubstitutionResolver resolver, - Set traversed, ConfigResolveOptions options, Path restrictToChildOrNull) + AbstractConfigObject resolveSubstitutions(SubstitutionResolver resolver, ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { AbstractConfigValue merged = ConfigDelayedMerge.resolveSubstitutions(stack, resolver, - traversed, options, restrictToChildOrNull); + context); if (merged instanceof AbstractConfigObject) { return (AbstractConfigObject) merged; } else { 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 31b6102b..6d9140cf 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java @@ -8,11 +8,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -136,30 +134,25 @@ final class ConfigSubstitution extends AbstractConfigValue implements private static AbstractConfigValue findInObject(AbstractConfigObject root, SubstitutionResolver resolver, /* null if we should not have refs */ - Path subst, Set traversed, ConfigResolveOptions options) + Path subst, ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { - AbstractConfigValue result = root.peekPath(subst, resolver, traversed, options); + AbstractConfigValue result = root.peekPath(subst, resolver, context); return result; } private AbstractConfigValue resolve(SubstitutionResolver resolver, - SubstitutionExpression subst, Set traversed, ConfigResolveOptions options, - Path restrictToChildOrNull) throws NotPossibleToResolve, + SubstitutionExpression subst, ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { - MemoKey key = new MemoKey(this, restrictToChildOrNull); - if (traversed.contains(key)) - throw new SelfReferential(origin(), subst.path().render()); - - traversed.add(key); + context.traverse(this, subst.path()); try { // 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.path(), - traversed, options); + context); if (result == null) { // Then we want to check relative to the root file. We don't @@ -169,28 +162,28 @@ final class ConfigSubstitution extends AbstractConfigValue implements Path unprefixed = subst.path().subPath(prefixLength); if (result == null && prefixLength > 0) { - result = findInObject(resolver.root(), resolver, unprefixed, traversed, options); + result = findInObject(resolver.root(), resolver, unprefixed, context); } - if (result == null && options.getUseSystemEnvironment()) { + if (result == null && context.options().getUseSystemEnvironment()) { result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, - unprefixed, traversed, options); + unprefixed, context); } } if (result != null) { - result = resolver.resolve(result, traversed, options, restrictToChildOrNull); + result = resolver.resolve(result, context); } return result; } finally { - traversed.remove(key); + context.untraverse(this); } } - private ConfigValue resolve(SubstitutionResolver resolver, Set traversed, - ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve { + private ConfigValue resolve(SubstitutionResolver resolver, ResolveContext context) + throws NotPossibleToResolve { if (pieces.size() > 1) { // need to concat everything into a string StringBuilder sb = new StringBuilder(); @@ -202,8 +195,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements ConfigValue v; try { // to concat into a string we have to do a full resolve, - // so don't pass along restrictToChildOrNull - v = resolve(resolver, exp, traversed, options, null); + // so unrestrict the context + v = resolve(resolver, exp, context.unrestricted()); } catch (NeedsFullResolve e) { throw new NotPossibleToResolve(null, exp.path().render(), "Some kind of loop or interdependency prevents resolving " + exp, e); @@ -237,7 +230,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0); ConfigValue v; try { - v = resolve(resolver, exp, traversed, options, restrictToChildOrNull); + v = resolve(resolver, exp, context); } catch (NeedsFullResolve e) { throw new NotPossibleToResolve(null, exp.path().render(), "Some kind of loop or interdependency prevents resolving " + exp, e); @@ -250,10 +243,9 @@ final class ConfigSubstitution extends AbstractConfigValue implements } @Override - AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, Set traversed, - ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve { - AbstractConfigValue resolved = (AbstractConfigValue) resolve(resolver, traversed, options, - restrictToChildOrNull); + AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, ResolveContext context) + throws NotPossibleToResolve { + AbstractConfigValue resolved = (AbstractConfigValue) resolve(resolver, context); return resolved; } diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java new file mode 100644 index 00000000..0c1635a7 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java @@ -0,0 +1,67 @@ +package com.typesafe.config.impl; + +import java.util.Set; +import java.util.LinkedHashSet; + +import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigResolveOptions; +import com.typesafe.config.impl.AbstractConfigValue.SelfReferential; + +final class ResolveContext { + // this set is unfortunately mutable and the user of ResolveContext + // has to be sure it's only shared between ResolveContext that + // are in the same traversal. + final private Set traversed; + final private ConfigResolveOptions options; + final private Path restrictToChild; // can be null + + ResolveContext(Set traversed, ConfigResolveOptions options, Path restrictToChild) { + this.traversed = traversed; + this.options = options; + this.restrictToChild = restrictToChild; + } + + ResolveContext(ConfigResolveOptions options, Path restrictToChild) { + // LinkedHashSet keeps the traversal order which is at least useful + // in error messages if nothing else + this(new LinkedHashSet(), options, restrictToChild); + } + + void traverse(ConfigSubstitution value, Path via) throws SelfReferential { + MemoKey key = new MemoKey(value, restrictToChild); + if (traversed.contains(key)) + throw new SelfReferential(value.origin(), via.render()); + + traversed.add(key); + } + + void untraverse(ConfigSubstitution value) { + MemoKey key = new MemoKey(value, restrictToChild); + if (!traversed.remove(key)) + throw new ConfigException.BugOrBroken( + "untraverse() did not find the untraversed substitution " + value); + } + + 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(traversed, options, restrictTo); + } + + ResolveContext unrestricted() { + return restrict(null); + } +} diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java index c7271231..589442b4 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -6,7 +6,6 @@ package com.typesafe.config.impl; import java.io.Serializable; import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -74,7 +73,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { Path path = Path.newPath(pathExpression); ConfigValue peeked; try { - peeked = object.peekPath(path, null, Collections. emptySet(), null); + peeked = object.peekPath(path, null, null); } catch (NotPossibleToResolve e) { throw e.exportException(origin(), pathExpression); } catch (NeedsFullResolve e) { diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java index aca3bf72..38c9f9c8 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -9,12 +9,10 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -106,13 +104,11 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { @Override SimpleConfigList resolveSubstitutions(final SubstitutionResolver resolver, - final Set traversed, final ConfigResolveOptions options, - Path restrictToChildOrNull) - throws NotPossibleToResolve, NeedsFullResolve { + final ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { if (resolved) return this; - if (restrictToChildOrNull != null) { + if (context.isRestrictedToChild()) { // if a list restricts to a child path, then it has no child paths, // so nothing to do. return this; @@ -122,7 +118,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { @Override public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve, NeedsFullResolve { - return resolver.resolve(v, traversed, options, null /* restrictToChild */); + return resolver.resolve(v, context); } }, ResolveStatus.RESOLVED); diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index aad2b8d4..cc9581c6 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -16,7 +16,6 @@ import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; final class SimpleConfigObject extends AbstractConfigObject { @@ -261,9 +260,7 @@ final class SimpleConfigObject extends AbstractConfigObject { @Override AbstractConfigObject resolveSubstitutions(final SubstitutionResolver resolver, - final Set traversed, final ConfigResolveOptions options, - final Path restrictToChildOrNull) - throws NotPossibleToResolve, NeedsFullResolve { + final ResolveContext context) throws NotPossibleToResolve, NeedsFullResolve { if (resolveStatus() == ResolveStatus.RESOLVED) return this; @@ -273,11 +270,11 @@ final class SimpleConfigObject extends AbstractConfigObject { @Override public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve, NeedsFullResolve { - if (restrictToChildOrNull != null) { - if (key.equals(restrictToChildOrNull.first())) { - Path remainder = restrictToChildOrNull.remainder(); + if (context.isRestrictedToChild()) { + if (key.equals(context.restrictToChild().first())) { + Path remainder = context.restrictToChild().remainder(); if (remainder != null) { - return resolver.resolve(v, traversed, options, remainder); + return resolver.resolve(v, context.restrict(remainder)); } else { // we don't want to resolve the leaf child. return v; @@ -288,7 +285,7 @@ final class SimpleConfigObject extends AbstractConfigObject { } } else { // no restrictToChild, resolve everything - return resolver.resolve(v, traversed, options, null); + return resolver.resolve(v, context.unrestricted()); } } 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 d3a975c2..a4909d0e 100644 --- a/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java +++ b/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java @@ -4,9 +4,7 @@ package com.typesafe.config.impl; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.Map; -import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigResolveOptions; @@ -29,9 +27,8 @@ final class SubstitutionResolver { this.memos = new HashMap(); } - AbstractConfigValue resolve(AbstractConfigValue original, Set traversed, - ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve, - NeedsFullResolve { + AbstractConfigValue resolve(AbstractConfigValue original, ResolveContext context) + throws NotPossibleToResolve, NeedsFullResolve { // a fully-resolved (no restrictToChild) object can satisfy a // request for a restricted object, so always check that first. @@ -43,16 +40,15 @@ final class SubstitutionResolver { // 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 && restrictToChildOrNull != null) { - restrictedKey = new MemoKey(original, restrictToChildOrNull); + if (cached == null && context.isRestrictedToChild()) { + restrictedKey = new MemoKey(original, context.restrictToChild()); cached = memos.get(restrictedKey); } if (cached != null) { return cached; } else { - AbstractConfigValue resolved = original.resolveSubstitutions(this, traversed, options, - restrictToChildOrNull); + AbstractConfigValue resolved = original.resolveSubstitutions(this, context); if (resolved == null || resolved.resolveStatus() == ResolveStatus.RESOLVED) { // if the resolved object is fully resolved by resolving @@ -64,15 +60,15 @@ final class SubstitutionResolver { // if we have an unresolved object then either we did a // partial resolve restricted to a certain child, or it's // a bug. - if (restrictToChildOrNull == null) { - throw new ConfigException.BugOrBroken( - "resolveSubstitutions() did not give us a resolved object"); - } else { + 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"); } } @@ -84,25 +80,19 @@ final class SubstitutionResolver { return this.root; } - private static Set newTraversedSet() { - // using LinkedHashSet just to show the order of traversal - // in error messages. - return new LinkedHashSet(); - } - static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root, ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve, NeedsFullResolve { SubstitutionResolver resolver = new SubstitutionResolver(root); - return resolver.resolve(value, newTraversedSet(), options, restrictToChildOrNull); + return resolver.resolve(value, new ResolveContext(options, restrictToChildOrNull)); } static AbstractConfigValue resolveWithExternalExceptions(AbstractConfigValue value, AbstractConfigObject root, ConfigResolveOptions options) { SubstitutionResolver resolver = new SubstitutionResolver(root); try { - return resolver.resolve(value, newTraversedSet(), options, null /* restrictToChild */); + return resolver.resolve(value, new ResolveContext(options, null /* restrictToChild */)); } catch (NotPossibleToResolve e) { throw e.exportException(value.origin(), null); } catch (NeedsFullResolve e) {