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 bcda80d7..a6863672 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -177,7 +177,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi } @Override - abstract AbstractConfigObject resolveSubstitutions(ResolveContext context, ResolveSource source) + abstract ResolveResult resolveSubstitutions(ResolveContext context, + ResolveSource source) throws NotPossibleToResolve; @Override 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 1eb05c81..0720417e 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -72,9 +72,9 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue { * where to look up values * @return a new value if there were changes, or this if no changes */ - AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source) + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { - return this; + return ResolveResult.make(context, this); } ResolveStatus resolveStatus() { diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java b/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java index e4f031eb..88f34ca9 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java @@ -170,7 +170,8 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab } @Override - AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) { int indent = context.depth() + 2; ConfigImpl.trace(indent - 1, "concatenation has " + pieces.size() + " pieces:"); @@ -185,12 +186,17 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab // content of ConfigConcatenation should not need to replaceChild, // but if it did we'd have to do this. ResolveSource sourceWithParent = source; // .pushParent(this); + ResolveContext newContext = context; List resolved = new ArrayList(pieces.size()); for (AbstractConfigValue p : pieces) { // to concat into a string we have to do a full resolve, - // so unrestrict the context - AbstractConfigValue r = context.unrestricted().resolve(p, sourceWithParent); + // so unrestrict the context, then put restriction back afterward + Path restriction = newContext.restrictToChild(); + ResolveResult result = newContext.unrestricted() + .resolve(p, sourceWithParent); + AbstractConfigValue r = result.value; + newContext = result.context.restrict(restriction); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(context.depth(), "resolved concat piece to " + r); if (r == null) { @@ -205,11 +211,12 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab // if unresolved is allowed we can just become another // ConfigConcatenation if (joined.size() > 1 && context.options().getAllowUnresolved()) - return new ConfigConcatenation(this.origin(), joined); + return ResolveResult.make(newContext, new ConfigConcatenation(this.origin(), joined)); else if (joined.isEmpty()) - return null; // we had just a list of optional references using ${?} + // we had just a list of optional references using ${?} + return ResolveResult.make(newContext, null); else if (joined.size() == 1) - return joined.get(0); + return ResolveResult.make(newContext, joined.get(0)); else throw new ConfigException.BugOrBroken("Bug in the library; resolved list was joined to too many values: " + joined); 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 32a8ea65..2893faf8 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -54,13 +54,14 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl } @Override - AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source) + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { return resolveSubstitutions(this, stack, context, source); } // static method also used by ConfigDelayedMergeObject - static AbstractConfigValue resolveSubstitutions(ReplaceableMergeStack replaceable, List stack, + static ResolveResult resolveSubstitutions(ReplaceableMergeStack replaceable, + List stack, ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) { ConfigImpl.trace(context.depth(), "delayed merge stack has " + stack.size() + " items:"); @@ -77,6 +78,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl // is non-null, or resolve options allow partial resolves, // we may remain a delayed merge though. + ResolveContext newContext = context; int count = 0; AbstractConfigValue merged = null; for (AbstractConfigValue end : stack) { @@ -92,7 +94,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl AbstractConfigValue remainder = replaceable.makeReplacement(context, count + 1); if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "remainder portion: " + remainder); + ConfigImpl.trace(newContext.depth(), "remainder portion: " + remainder); // If, while resolving 'end' we come back to the same // merge stack, we only want to look _below_ 'end' @@ -101,40 +103,43 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl // the remainder of the stack below this one. if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "building sourceForEnd"); + ConfigImpl.trace(newContext.depth(), "building sourceForEnd"); // we resetParents() here because we'll be resolving "end" // against a root which does NOT contain "end" sourceForEnd = source.replaceWithinCurrentParent((AbstractConfigValue) replaceable, remainder); if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), " sourceForEnd before reset parents but after replace: " + ConfigImpl.trace(newContext.depth(), " sourceForEnd before reset parents but after replace: " + sourceForEnd); sourceForEnd = sourceForEnd.resetParents(); } else { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl - .trace(context.depth(), "will resolve end against the original source with parent pushed"); +.trace(newContext.depth(), + "will resolve end against the original source with parent pushed"); sourceForEnd = source.pushParent(replaceable); } if (ConfigImpl.traceSubstitutionsEnabled()) { - ConfigImpl.trace(context.depth(), "sourceForEnd =" + sourceForEnd); + ConfigImpl.trace(newContext.depth(), "sourceForEnd =" + sourceForEnd); } if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "Resolving highest-priority item in delayed merge " + end + ConfigImpl.trace(newContext.depth(), "Resolving highest-priority item in delayed merge " + end + " against " + sourceForEnd + " endWasRemoved=" + (source != sourceForEnd)); - AbstractConfigValue resolvedEnd = context.resolve(end, sourceForEnd); + ResolveResult result = newContext.resolve(end, sourceForEnd); + AbstractConfigValue resolvedEnd = result.value; + newContext = result.context; if (resolvedEnd != null) { if (merged == null) { merged = resolvedEnd; } else { if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd); + ConfigImpl.trace(newContext.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd); merged = merged.withFallback(resolvedEnd); } } @@ -142,10 +147,10 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl count += 1; if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "stack merged, yielding: " + merged); + ConfigImpl.trace(newContext.depth(), "stack merged, yielding: " + merged); } - return merged; + return ResolveResult.make(newContext, merged); } @Override 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 f9afd6d7..7aa3ef97 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -50,14 +50,11 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm } @Override - AbstractConfigObject resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { - AbstractConfigValue merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, context, source); - if (merged instanceof AbstractConfigObject) { - return (AbstractConfigObject) merged; - } else { - throw new ConfigException.BugOrBroken( - "somehow brokenly merged an object and didn't get an object, got " + merged); - } + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + ResolveResult merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, + context, source); + return merged.asObjectResult(); } @Override diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigReference.java b/config/src/main/java/com/typesafe/config/impl/ConfigReference.java index 963157cb..8d3c7c0c 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigReference.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigReference.java @@ -65,49 +65,49 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable { // This way it's impossible for NotPossibleToResolve to "escape" since // any failure to resolve has to start with a ConfigReference. @Override - AbstractConfigValue resolveSubstitutions(ResolveContext context, ResolveSource source) { - context.addCycleMarker(this); + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) { + ResolveContext newContext = context.addCycleMarker(this); + AbstractConfigValue v; try { - AbstractConfigValue v; - try { - ResolveSource.ValueWithPath valueWithPath = source.lookupSubst(context, expr, prefixLength); + ResolveSource.ResultWithPath resultWithPath = source.lookupSubst(newContext, expr, prefixLength); + newContext = resultWithPath.result.context; - if (valueWithPath.value != null) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "recursively resolving " + valueWithPath - + " which was the resolution of " + expr + " against " + source); - - ResolveSource recursiveResolveSource = (new ResolveSource( - (AbstractConfigObject) valueWithPath.pathFromRoot.last(), valueWithPath.pathFromRoot)); - - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "will recursively resolve against " + recursiveResolveSource); - - v = context.resolve(valueWithPath.value, recursiveResolveSource); - } else { - v = null; - } - } catch (NotPossibleToResolve e) { + if (resultWithPath.result.value != null) { if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), - "not possible to resolve " + expr + ", cycle involved: " + e.traceString()); - if (expr.optional()) - v = null; - else - throw new ConfigException.UnresolvedSubstitution(origin(), expr - + " was part of a cycle of substitutions involving " + e.traceString(), e); - } + ConfigImpl.trace(newContext.depth(), "recursively resolving " + resultWithPath + + " which was the resolution of " + expr + " against " + source); - if (v == null && !expr.optional()) { - if (context.options().getAllowUnresolved()) - return this; - else - throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString()); + ResolveSource recursiveResolveSource = (new ResolveSource( + (AbstractConfigObject) resultWithPath.pathFromRoot.last(), resultWithPath.pathFromRoot)); + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "will recursively resolve against " + recursiveResolveSource); + + ResolveResult result = newContext.resolve(resultWithPath.result.value, + recursiveResolveSource); + v = result.value; + newContext = result.context; } else { - return v; + v = null; } - } finally { - context.removeCycleMarker(this); + } catch (NotPossibleToResolve e) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), + "not possible to resolve " + expr + ", cycle involved: " + e.traceString()); + if (expr.optional()) + v = null; + else + throw new ConfigException.UnresolvedSubstitution(origin(), expr + + " was part of a cycle of substitutions involving " + e.traceString(), e); + } + + if (v == null && !expr.optional()) { + if (newContext.options().getAllowUnresolved()) + return ResolveResult.make(newContext.removeCycleMarker(this), this); + else + throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString()); + } else { + return ResolveResult.make(newContext.removeCycleMarker(this), v); } } 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 49b1ec22..076d07fe 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java @@ -11,8 +11,6 @@ import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; final class ResolveContext { - // this is unfortunately mutable so should only be shared among - // ResolveContext in the same traversal. final private ResolveMemos memos; final private ConfigResolveOptions options; @@ -34,31 +32,46 @@ final class ResolveContext { this.memos = memos; this.options = options; this.restrictToChild = restrictToChild; - this.resolveStack = resolveStack; - this.cycleMarkers = cycleMarkers; + this.resolveStack = Collections.unmodifiableList(resolveStack); + this.cycleMarkers = Collections.unmodifiableSet(cycleMarkers); + } + + private static Set newCycleMarkers() { + return Collections.newSetFromMap(new IdentityHashMap()); } ResolveContext(ConfigResolveOptions options, Path restrictToChild) { // LinkedHashSet keeps the traversal order which is at least useful // in error messages if nothing else - this(new ResolveMemos(), options, restrictToChild, new ArrayList(), Collections - .newSetFromMap(new IdentityHashMap())); + this(new ResolveMemos(), options, restrictToChild, new ArrayList(), newCycleMarkers()); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "ResolveContext restrict to child " + restrictToChild); } - void addCycleMarker(AbstractConfigValue value) { + ResolveContext addCycleMarker(AbstractConfigValue value) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "++ Cycle marker " + value + "@" + System.identityHashCode(value)); if (cycleMarkers.contains(value)) throw new ConfigException.BugOrBroken("Added cycle marker twice " + value); - cycleMarkers.add(value); + Set copy = newCycleMarkers(); + copy.addAll(cycleMarkers); + copy.add(value); + return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); } - void removeCycleMarker(AbstractConfigValue value) { - cycleMarkers.remove(value); + ResolveContext removeCycleMarker(AbstractConfigValue value) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "-- Cycle marker " + value + "@" + System.identityHashCode(value)); + + Set copy = newCycleMarkers(); + copy.addAll(cycleMarkers); + copy.remove(value); + return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); + } + + private ResolveContext memoize(MemoKey key, AbstractConfigValue value) { + ResolveMemos changed = memos.put(key, value); + return new ResolveContext(changed, options, restrictToChild, resolveStack, cycleMarkers); } ConfigResolveOptions options() { @@ -73,6 +86,7 @@ final class ResolveContext { return restrictToChild; } + // restrictTo may be null to unrestrict ResolveContext restrict(Path restrictTo) { if (restrictTo == restrictToChild) return this; @@ -98,16 +112,20 @@ final class ResolveContext { return sb.toString(); } - private void pushTrace(AbstractConfigValue value) { + private ResolveContext pushTrace(AbstractConfigValue value) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "pushing trace " + value); - resolveStack.add(value); + List copy = new ArrayList(resolveStack); + copy.add(value); + return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); } - private void popTrace() { - AbstractConfigValue old = resolveStack.remove(resolveStack.size() - 1); + ResolveContext popTrace() { + List copy = new ArrayList(resolveStack); + AbstractConfigValue old = copy.remove(resolveStack.size() - 1); if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "popped trace " + old); + ConfigImpl.trace(depth() - 1, "popped trace " + old); + return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); } int depth() { @@ -116,21 +134,15 @@ final class ResolveContext { return resolveStack.size(); } - AbstractConfigValue resolve(AbstractConfigValue original, ResolveSource source) throws NotPossibleToResolve { + ResolveResult resolve(AbstractConfigValue original, ResolveSource source) + throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl .trace(depth(), "resolving " + original + " restrictToChild=" + restrictToChild + " in " + source); - AbstractConfigValue resolved; - pushTrace(original); - try { - resolved = realResolve(original, source); - } finally { - popTrace(); - } - return resolved; + return pushTrace(original).realResolve(original, source).popTrace(); } - private AbstractConfigValue realResolve(AbstractConfigValue original, ResolveSource source) + private ResolveResult realResolve(AbstractConfigValue original, ResolveSource source) throws NotPossibleToResolve { // a fully-resolved (no restrictToChild) object can satisfy a // request for a restricted object, so always check that first. @@ -151,7 +163,7 @@ final class ResolveContext { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "using cached resolution " + cached + " for " + original + " restrictToChild " + restrictToChild()); - return cached; + return ResolveResult.make(this, cached); } else { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), @@ -164,12 +176,15 @@ final class ResolveContext { throw new NotPossibleToResolve(this); } - AbstractConfigValue resolved = original.resolveSubstitutions(this, source); + ResolveResult result = original.resolveSubstitutions(this, source); + AbstractConfigValue resolved = result.value; if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "resolved to " + resolved + "@" + System.identityHashCode(resolved) + " from " + original + "@" + System.identityHashCode(resolved)); + ResolveContext withMemo = result.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 @@ -178,7 +193,7 @@ final class ResolveContext { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); - memos.put(fullKey, resolved); + withMemo = withMemo.memoize(fullKey, resolved); } else { // if we have an unresolved object then either we did a // partial resolve restricted to a certain child, or we are @@ -191,19 +206,19 @@ final class ResolveContext { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "caching " + restrictedKey + " result " + resolved); - memos.put(restrictedKey, resolved); + withMemo = withMemo.memoize(restrictedKey, resolved); } else if (options().getAllowUnresolved()) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); - memos.put(fullKey, resolved); + withMemo = withMemo.memoize(fullKey, resolved); } else { throw new ConfigException.BugOrBroken( "resolveSubstitutions() did not give us a resolved object"); } } - return resolved; + return ResolveResult.make(withMemo, resolved); } } @@ -213,7 +228,7 @@ final class ResolveContext { ResolveContext context = new ResolveContext(options, null /* restrictToChild */); try { - return context.resolve(value, source); + return context.resolve(value, source).value; } catch (NotPossibleToResolve e) { // ConfigReference was supposed to catch NotPossibleToResolve throw new ConfigException.BugOrBroken( diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveMemos.java b/config/src/main/java/com/typesafe/config/impl/ResolveMemos.java index dc43d727..cf98a900 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveMemos.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveMemos.java @@ -13,15 +13,23 @@ final class ResolveMemos { // rather than ConfigNull) so this map can have null values. final private Map memos; + private ResolveMemos(Map memos) { + this.memos = memos; + } + ResolveMemos() { - this.memos = new HashMap(); + this(new HashMap()); } AbstractConfigValue get(MemoKey key) { return memos.get(key); } - void put(MemoKey key, AbstractConfigValue value) { - memos.put(key, value); + ResolveMemos put(MemoKey key, AbstractConfigValue value) { + // completely inefficient, but so far nobody cares about resolve() + // performance, we can clean it up someday... + Map copy = new HashMap(memos); + copy.put(key, value); + return new ResolveMemos(copy); } } diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveResult.java b/config/src/main/java/com/typesafe/config/impl/ResolveResult.java new file mode 100644 index 00000000..28e26c00 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/ResolveResult.java @@ -0,0 +1,43 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.ConfigException; + +// value is allowed to be null +final class ResolveResult { + public final ResolveContext context; + public final V value; + + private ResolveResult(ResolveContext context, V value) { + this.context = context; + this.value = value; + } + + static ResolveResult make(ResolveContext context, V value) { + return new ResolveResult(context, value); + } + + // better option? we don't have variance + @SuppressWarnings("unchecked") + ResolveResult asObjectResult() { + if (!(value instanceof AbstractConfigObject)) + throw new ConfigException.BugOrBroken("Expecting a resolve result to be an object, but it was " + value); + Object o = this; + return (ResolveResult) o; + } + + // better option? we don't have variance + @SuppressWarnings("unchecked") + ResolveResult asValueResult() { + Object o = this; + return (ResolveResult) o; + } + + ResolveResult popTrace() { + return make(context.popTrace(), value); + } + + @Override + public String toString() { + return "ResolveResult(" + value + ")"; + } +} 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 514c38a7..6eea30a3 100644 --- a/config/src/main/java/com/typesafe/config/impl/ResolveSource.java +++ b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java @@ -41,14 +41,18 @@ final class ResolveSource { // child being peeked, but NOT the child itself. Caller has to resolve // the child itself if needed. ValueWithPath.value can be null but // the ValueWithPath instance itself should not be. - static private ValueWithPath findInObject(AbstractConfigObject obj, ResolveContext context, Path path) + static private ResultWithPath findInObject(AbstractConfigObject obj, ResolveContext context, Path path) throws NotPossibleToResolve { // resolve ONLY portions of the object which are along our path if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace("*** finding '" + path + "' in " + obj); - AbstractConfigValue partiallyResolved = context.restrict(path).resolve(obj, new ResolveSource(obj)); - if (partiallyResolved instanceof AbstractConfigObject) { - return findInObject((AbstractConfigObject) partiallyResolved, path); + Path restriction = context.restrictToChild(); + ResolveResult partiallyResolved = context.restrict(path).resolve(obj, + new ResolveSource(obj)); + ResolveContext newContext = partiallyResolved.context.restrict(restriction); + if (partiallyResolved.value instanceof AbstractConfigObject) { + ValueWithPath pair = findInObject((AbstractConfigObject) partiallyResolved.value, path); + return new ResultWithPath(ResolveResult.make(newContext, pair.value), pair.pathFromRoot); } else { throw new ConfigException.BugOrBroken("resolved object to non-object " + obj + " to " + partiallyResolved); } @@ -83,7 +87,7 @@ final class ResolveSource { } } - ValueWithPath lookupSubst(ResolveContext context, SubstitutionExpression subst, + ResultWithPath lookupSubst(ResolveContext context, SubstitutionExpression subst, int prefixLength) throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) @@ -93,12 +97,9 @@ final class ResolveSource { ConfigImpl.trace(context.depth(), subst + " - looking up relative to file it occurred in"); // First we look up the full path, which means relative to the // included file if we were not a root file - ValueWithPath result = findInObject(root, context, subst.path()); + ResultWithPath result = findInObject(root, context, subst.path()); - if (result == null) - throw new ConfigException.BugOrBroken("findInObject() returned null"); - - if (result.value == null) { + if (result.result.value == 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. @@ -106,19 +107,20 @@ final class ResolveSource { if (prefixLength > 0) { if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), unprefixed + " - looking up relative to parent file"); - result = findInObject(root, context, unprefixed); + ConfigImpl.trace(result.result.context.depth(), unprefixed + + " - looking up relative to parent file"); + result = findInObject(root, result.result.context, unprefixed); } - if (result.value == null && context.options().getUseSystemEnvironment()) { + if (result.result.value == null && result.result.context.options().getUseSystemEnvironment()) { if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), unprefixed + " - looking up in system environment"); + ConfigImpl.trace(result.result.context.depth(), unprefixed + " - looking up in system environment"); result = findInObject(ConfigImpl.envVariablesAsConfigObject(), context, unprefixed); } } if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "resolved to " + result); + ConfigImpl.trace(result.result.context.depth(), "resolved to " + result); return result; } @@ -391,4 +393,30 @@ final class ResolveSource { return "ValueWithPath(value=" + value + ", pathFromRoot=" + pathFromRoot + ")"; } } + + static final class ResultWithPath { + final ResolveResult result; + final Node pathFromRoot; + + ResultWithPath(ResolveResult result, Node pathFromRoot) { + this.result = result; + this.pathFromRoot = pathFromRoot; + } + + ResultWithPath(ResolveResult result) { + this(result, null); + } + + ResultWithPath addParent(Container parent) { + if (pathFromRoot == null) + return new ResultWithPath(result, new Node(parent)); + else + return new ResultWithPath(result, pathFromRoot.prepend(parent)); + } + + @Override + public String toString() { + return "ResultWithPath(result=" + result + ", pathFromRoot=" + pathFromRoot + ")"; + } + } } 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 038e0b9e..77ad5c6a 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -121,27 +121,38 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList, } } + private static class ResolveModifier implements Modifier { + ResolveContext context; + final ResolveSource source; + ResolveModifier(ResolveContext context, ResolveSource source) { + this.context = context; + this.source = source; + } + + @Override + public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) + throws NotPossibleToResolve { + ResolveResult result = context.resolve(v, source); + context = result.context; + return result.value; + } + } + @Override - SimpleConfigList resolveSubstitutions(final ResolveContext context, ResolveSource source) + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (resolved) - return this; + return ResolveResult.make(context, this); if (context.isRestrictedToChild()) { // if a list restricts to a child path, then it has no child paths, // so nothing to do. - return this; + return ResolveResult.make(context, this); } else { - final ResolveSource sourceWithParent = source.pushParent(this); try { - return modifyMayThrow(new Modifier() { - @Override - public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) - throws NotPossibleToResolve { - return context.resolve(v, sourceWithParent); - } - - }, ResolveStatus.RESOLVED); + ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this)); + SimpleConfigList value = modifyMayThrow(modifier, ResolveStatus.RESOLVED); + return ResolveResult.make(modifier.context, value); } catch (NotPossibleToResolve e) { throw e; } catch (RuntimeException e) { 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 e652af61..fbab0a46 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -344,41 +344,59 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa } } + private static final class ResolveModifier implements Modifier { + + final Path originalRestrict; + ResolveContext context; + final ResolveSource source; + + ResolveModifier(ResolveContext context, ResolveSource source) { + this.context = context; + this.source = source; + originalRestrict = context.restrictToChild(); + } + + @Override + public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve { + if (context.isRestrictedToChild()) { + if (key.equals(context.restrictToChild().first())) { + Path remainder = context.restrictToChild().remainder(); + if (remainder != null) { + ResolveResult result = context.restrict(remainder).resolve(v, + source); + context = result.context.unrestricted().restrict(originalRestrict); + return result.value; + } else { + // we don't want to resolve the leaf child. + return v; + } + } else { + // not in the restrictToChild path + return v; + } + } else { + // no restrictToChild, resolve everything + ResolveResult result = context.unrestricted().resolve(v, source); + context = result.context.unrestricted().restrict(originalRestrict); + return result.value; + } + } + + } + @Override - AbstractConfigObject resolveSubstitutions(final ResolveContext context, ResolveSource source) + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (resolveStatus() == ResolveStatus.RESOLVED) - return this; + return ResolveResult.make(context, this); final ResolveSource sourceWithParent = source.pushParent(this); try { - return modifyMayThrow(new Modifier() { + ResolveModifier modifier = new ResolveModifier(context, sourceWithParent); - @Override - public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) - throws NotPossibleToResolve { - - if (context.isRestrictedToChild()) { - if (key.equals(context.restrictToChild().first())) { - Path remainder = context.restrictToChild().remainder(); - if (remainder != null) { - return context.restrict(remainder).resolve(v, sourceWithParent); - } else { - // we don't want to resolve the leaf child. - return v; - } - } else { - // not in the restrictToChild path - return v; - } - } else { - // no restrictToChild, resolve everything - return context.unrestricted().resolve(v, sourceWithParent); - } - } - - }); + AbstractConfigValue value = modifyMayThrow(modifier); + return ResolveResult.make(modifier.context, value).asObjectResult(); } catch (NotPossibleToResolve e) { throw e; } catch (RuntimeException e) {