The basic idea in this patch is to unify on the "replacer"
(modifying the tree in which we lookup substitutions)
mechanism for detecting cycles.
Drop the "traverse" method, instead keeping a trace of expressions
we've passed through for nice error messages.
There is now only one internal checked exception possible,
NotPossibleToResolve which is thrown when a cycle is detected;
this checked exception is always caught by ConfigReference
so cannot "escape" from the library. The exception exists
because we want to get the traceString debug info out
of the spot that detected the cycle, if we didn't want that
debug info we could just return null as usual for undefined.
As part of simplifying this (which should also simplify the
spec), resolutions which require double-traverse of the same
reference are no longer supported:
a=1, b=${a}, a=${b}
Also, cycles now always throw UnresolvedSubstitution rather
than BadValue. This was needed for consistency since
conceptually a single a=${a} is going to "look back" earlier
in the merge stack, discover there is no earlier value of a,
and fail; it should be the same exception as a=${a},a={b:1},
and in both cases referring to these cycles via ${?} should
hide the exception.
This makes the code a good bit simpler to reason about, the old
ConfigSubstitution really mixed two things into one class.
Unfortunately old ConfigSubstitution is hanging around as a compat
shim so we can deserialize stuff from the old version of the library.
The old version will not be able to deserialize unresolved configs
from this new version.
In retrospect it might have been better to forbid serialization
of unresolved configs and only support serializing resolved configs.
The MemoKey stuff here was leftover from some earlier version of
the code, and was no longer used (the restrictToChild was always
null in the MemoKey).
this is part of a plan to remove SubstitutionResolver (moving the
memoized hash to ResolveContext) and to move the "replacements"
functionality to ResolveSource. Should be clearer.
I think this was a relic of some earlier idea about how to do
things. It was used in one place that could just use the
public NotResolved exception, since it was converted to that
later anyhow.
To avoid error-message regression, we still catch NotResolved
and change it into a NotResolved with a different message in
certain cases.
Also clean up the wording of NotResolved in various places.
Probably NotResolved could just have a zero-parameters constructor.
With this patch, you can write:
path="a🅱️c"
path=${path}":d"
The semantics are somewhat complicated to specify and
implement, but hopefully not complicated to use for
any reasonable usage that makes any sense.
This patch is technically backward incompatible,
illustrated by a change to existing unit tests,
but the changed behavior is in a bizarre corner case:
cyclical self-references where the cycle could be broken
by only partially resolving an object.
This corner case just threw an exception in the version
of this lib included with Akka/Play 2.0, but in commit
a59e31f744 we tried to handle the case. So the behavior
changes since that commit.
The partial resolution case now has the same consistent
defined semantics we use for all self-reference
resolution, that is it "looks back." In the previous
code it would "look forward."
This makes it easier to mess with the parameters needed without
changing every resolveSubstitutions() all over the place.
Really the SubstitutionResolver and ResolveContext should maybe
be merged, but keeping this patch more incremental.
This is probably not different from the depth counter
in practical situations, but it does guarantee 100%
that we are only detecting true cycles. Since we want
true cycles to have different semantics (in a following
patch), I feel better about this implementation.
So we get any reference.conf from the context class loader.
This does NOT fix loading non-default configs, we need new API
to allow passing in a class loader for that. It also makes
things a bit less efficient since it no longer caches;
in the future we could do a per-class-loader cache.
The main idea of this patch is to introduce "partial resolution"
which means resolving only the minimum branch of the object tree
to get to a desired value. By using partial resolution whenever
possible, more interdependencies between substitutions are permitted.
ConfigDelayedMergeObject was a big problem because a lot of the
code in AbstractConfigObject really didn't work on it, because
it assumed a resolved object; much of that code now moves down
to SimpleConfigObject.