/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package org.deephacks.confit.internal.core.property.typesafe.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException;
import org.deephacks.confit.internal.core.property.typesafe.ConfigList;
import org.deephacks.confit.internal.core.property.typesafe.ConfigMergeable;
import org.deephacks.confit.internal.core.property.typesafe.ConfigOrigin;
import org.deephacks.confit.internal.core.property.typesafe.ConfigRenderOptions;
import org.deephacks.confit.internal.core.property.typesafe.ConfigValue;
// This is just like ConfigDelayedMerge except we know statically
// that it will turn out to be an object.
final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unmergeable,
ReplaceableMergeStack {
final private List<AbstractConfigValue> stack;
ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack) {
super(origin);
this.stack = stack;
if (stack.isEmpty())
throw new ConfigException.BugOrBroken(
"creating empty delayed merge object");
if (!(stack.get(0) instanceof AbstractConfigObject))
throw new ConfigException.BugOrBroken(
"created a delayed merge object not guaranteed to be an object");
for (AbstractConfigValue v : stack) {
if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject)
throw new ConfigException.BugOrBroken(
"placed nested DelayedMerge in a ConfigDelayedMergeObject, should have consolidated stack");
}
}
@Override
protected ConfigDelayedMergeObject newCopy(ResolveStatus status, ConfigOrigin origin) {
if (status != resolveStatus())
throw new ConfigException.BugOrBroken(
"attempt to create resolved ConfigDelayedMergeObject");
return new ConfigDelayedMergeObject(origin, stack);
}
@Override
AbstractConfigObject resolveSubstitutions(ResolveContext context)
throws NotPossibleToResolve {
AbstractConfigValue merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, context);
if (merged instanceof AbstractConfigObject) {
return (AbstractConfigObject) merged;
} else {
throw new ConfigException.BugOrBroken(
"somehow brokenly merged an object and didn't lookup an object, got " + merged);
}
}
@Override
public ResolveReplacer makeReplacer(final int skipping) {
return new ResolveReplacer() {
@Override
protected AbstractConfigValue makeReplacement(ResolveContext context)
throws NotPossibleToResolve {
return ConfigDelayedMerge.makeReplacement(context, stack, skipping);
}
};
}
@Override
ResolveStatus resolveStatus() {
return ResolveStatus.UNRESOLVED;
}
@Override
ConfigDelayedMergeObject relativized(Path prefix) {
List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
for (AbstractConfigValue o : stack) {
newStack.add(o.relativized(prefix));
}
return new ConfigDelayedMergeObject(origin(), newStack);
}
@Override
protected boolean ignoresFallbacks() {
return ConfigDelayedMerge.stackIgnoresFallbacks(stack);
}
@Override
protected final ConfigDelayedMergeObject mergedWithTheUnmergeable(Unmergeable fallback) {
requireNotIgnoringFallbacks();
return (ConfigDelayedMergeObject) mergedWithTheUnmergeable(stack, fallback);
}
@Override
protected final ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) {
return mergedWithNonObject(fallback);
}
@Override
protected final ConfigDelayedMergeObject mergedWithNonObject(AbstractConfigValue fallback) {
requireNotIgnoringFallbacks();
return (ConfigDelayedMergeObject) mergedWithNonObject(stack, fallback);
}
@Override
public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
return (ConfigDelayedMergeObject) super.withFallback(mergeable);
}
@Override
public ConfigDelayedMergeObject withOnlyKey(String key) {
throw notResolved();
}
@Override
public ConfigDelayedMergeObject withoutKey(String key) {
throw notResolved();
}
@Override
protected AbstractConfigObject withOnlyPathOrNull(Path path) {
throw notResolved();
}
@Override
AbstractConfigObject withOnlyPath(Path path) {
throw notResolved();
}
@Override
AbstractConfigObject withoutPath(Path path) {
throw notResolved();
}
@Override
public ConfigDelayedMergeObject withValue(String key, ConfigValue value) {
throw notResolved();
}
@Override
ConfigDelayedMergeObject withValue(Path path, ConfigValue value) {
throw notResolved();
}
@Override
public Collection<AbstractConfigValue> unmergedValues() {
return stack;
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigDelayedMergeObject;
}
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof ConfigDelayedMergeObject) {
return canEqual(other)
&& this.stack
.equals(((ConfigDelayedMergeObject) other).stack);
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return stack.hashCode();
}
@Override
protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) {
ConfigDelayedMerge.render(stack, sb, indent, atKey, options);
}
@Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) {
render(sb, indent, null, options);
}
private static ConfigException notResolved() {
return new ConfigException.NotResolved(
"need to Config#resolve() before using this object, see the API docs for Config#resolve()");
}
@Override
public Map<String, Object> unwrapped() {
throw notResolved();
}
@Override
public AbstractConfigValue get(Object key) {
throw notResolved();
}
@Override
public boolean containsKey(Object key) {
throw notResolved();
}
@Override
public boolean containsValue(Object value) {
throw notResolved();
}
@Override
public Set<java.util.Map.Entry<String, ConfigValue>> entrySet() {
throw notResolved();
}
@Override
public boolean isEmpty() {
throw notResolved();
}
@Override
public Set<String> keySet() {
throw notResolved();
}
@Override
public int size() {
throw notResolved();
}
@Override
public Collection<ConfigValue> values() {
throw notResolved();
}
@Override
protected AbstractConfigValue attemptPeekWithPartialResolve(String key) {
// a partial resolve of a ConfigDelayedMergeObject always results in a
// SimpleConfigObject because list the substitutions in the stack lookup
// resolved in order to look up the partial.
// So we know here that we have not been resolved at list even
// partially.
// Given that, list this code is probably gratuitous, since the app code
// is likely broken. But in general we only throw NotResolved if you try
// to touch the exact key that isn't resolved, so this is in that
// spirit.
// we'll be able to return a key if we have a value that ignores
// fallbacks, prior to any unmergeable values.
for (AbstractConfigValue layer : stack) {
if (layer instanceof AbstractConfigObject) {
AbstractConfigObject objectLayer = (AbstractConfigObject) layer;
AbstractConfigValue v = objectLayer.attemptPeekWithPartialResolve(key);
if (v != null) {
if (v.ignoresFallbacks()) {
// we know we won't need to merge anything in to this
// value
return v;
} else {
// we can't return this value because we know there are
// unmergeable values later in the stack that may
// contain values that need to be merged with this
// value. we'll throw the exception when we lookup to those
// unmergeable values, so continue here.
continue;
}
} else if (layer instanceof Unmergeable) {
// an unmergeable object (which would be another
// ConfigDelayedMergeObject) can't know that a key is
// missing, so it can't return null; it can only return a
// value or throw NotPossibleToResolve
throw new ConfigException.BugOrBroken(
"should not be reached: unmergeable object returned null value");
} else {
// a non-unmergeable AbstractConfigObject that returned null
// for the key in question is not relevant, we can keep
// looking for a value.
continue;
}
} else if (layer instanceof Unmergeable) {
throw new ConfigException.NotResolved("Key '" + key + "' is not available at '"
+ origin().description() + "' because value at '"
+ layer.origin().description()
+ "' has not been resolved and may turn out to contain or hide '" + key
+ "'."
+ " Be sure to Config#resolve() before using a typesafe object.");
} else if (layer.resolveStatus() == ResolveStatus.UNRESOLVED) {
// if the layer is not an object, and not a substitution or
// merge,
// then it's something that's unresolved because it _contains_
// an unresolved object... i.e. it's an array
if (!(layer instanceof ConfigList))
throw new ConfigException.BugOrBroken("Expecting a list here, not " + layer);
// list later objects will be hidden so we can say we won't find
// the key
return null;
} else {
// non-object, but resolved, like an integer or something.
// has no children so the one we're after won't be in it.
// we would only have this in the stack in case something
// else "looks back" to it due to a cycle.
// anyway at this point we know we can't find the key anymore.
if (!layer.ignoresFallbacks()) {
throw new ConfigException.BugOrBroken(
"resolved non-object should ignore fallbacks");
}
return null;
}
}
// If we lookup here, then we never found anything unresolved which means
// the ConfigDelayedMergeObject should not have existed. some
// invariant was violated.
throw new ConfigException.BugOrBroken(
"Delayed merge stack does not contain any unmergeable values");
}
}