package org.deephacks.confit.internal.core.property.typesafe.impl; import java.util.IdentityHashMap; import java.util.Map; import org.deephacks.confit.internal.core.property.typesafe.ConfigException; import org.deephacks.confit.internal.core.property.typesafe.impl.AbstractConfigValue.NotPossibleToResolve; /** * 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<AbstractConfigValue, ResolveReplacer> replacements; ResolveSource(AbstractConfigObject root) { this.root = root; this.replacements = new IdentityHashMap<AbstractConfigValue, ResolveReplacer>(); } static private AbstractConfigValue findInObject(AbstractConfigObject obj, ResolveContext context, SubstitutionExpression subst) throws NotPossibleToResolve { return obj.peekPath(subst.path(), context); } AbstractConfigValue lookupSubst(ResolveContext context, SubstitutionExpression subst, int prefixLength) throws NotPossibleToResolve { context.trace(subst); 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(root, context, 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)); // replace the debug trace path context.untrace(); context.trace(unprefixed); if (prefixLength > 0) { result = findInObject(root, context, unprefixed); } if (result == null && context.options().getUseSystemEnvironment()) { result = findInObject(ConfigImpl.envVariablesAsConfigObject(), context, unprefixed); } } if (result != null) { result = context.resolve(result); } return result; } finally { context.untrace(); } } void replace(AbstractConfigValue value, ResolveReplacer replacer) { ResolveReplacer old = replacements.put(value, replacer); if (old != null) throw new ConfigException.BugOrBroken("should not have replaced the same value twice: " + value); } void unreplace(AbstractConfigValue value) { ResolveReplacer replacer = replacements.remove(value); if (replacer == null) throw new ConfigException.BugOrBroken("unreplace() without replace(): " + value); } private AbstractConfigValue replacement(ResolveContext context, AbstractConfigValue value) throws NotPossibleToResolve { ResolveReplacer replacer = replacements.get(value); if (replacer == null) { return value; } else { return replacer.replace(context); } } /** * Conceptually, this is key.value().resolveSubstitutions() but using the * replacement for key.value() if any. */ AbstractConfigValue resolveCheckingReplacement(ResolveContext context, AbstractConfigValue original) throws NotPossibleToResolve { AbstractConfigValue replacement; replacement = replacement(context, original); if (replacement != original) { // start over, checking if replacement was memoized return context.resolve(replacement); } else { AbstractConfigValue resolved; resolved = original.resolveSubstitutions(context); return resolved; } } }