/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package org.deephacks.confit.internal.core.property.typesafe.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.deephacks.confit.internal.core.property.typesafe.Config;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException.Missing;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException.NotResolved;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException.Null;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException.WrongType;
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.ConfigResolveOptions;
import org.deephacks.confit.internal.core.property.typesafe.ConfigValueType;
import org.deephacks.confit.internal.core.property.typesafe.ConfigObject;
import org.deephacks.confit.internal.core.property.typesafe.ConfigOrigin;
import org.deephacks.confit.internal.core.property.typesafe.ConfigValue;
/**
* One thing to keep in mind in the future: as Collection-like APIs are added
* here, including iterators or size() or anything, they should be consistent
* with a one-level java.util.Map from paths to non-null values. Null values are
* not "in" the map.
*/
final class SimpleConfig implements Config, MergeableValue, Serializable {
private static final long serialVersionUID = 1L;
final private AbstractConfigObject object;
SimpleConfig(AbstractConfigObject object) {
this.object = object;
}
@Override
public AbstractConfigObject root() {
return object;
}
@Override
public ConfigOrigin origin() {
return object.origin();
}
@Override
public SimpleConfig resolve() {
return resolve(ConfigResolveOptions.defaults());
}
@Override
public SimpleConfig resolve(ConfigResolveOptions options) {
AbstractConfigValue resolved = ResolveContext.resolve(object, object, options);
if (resolved == object)
return this;
else
return new SimpleConfig((AbstractConfigObject) resolved);
}
@Override
public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
ConfigValue peeked;
try {
peeked = object.peekPath(path);
} catch (NotResolved e) {
throw ConfigImpl.improveNotResolved(path, e);
}
return peeked != null && peeked.valueType() != ConfigValueType.NULL;
}
@Override
public boolean isEmpty() {
return object.isEmpty();
}
private static void findPaths(Set<Map.Entry<String, ConfigValue>> entries, Path parent,
AbstractConfigObject obj) {
for (Map.Entry<String, ConfigValue> entry : obj.entrySet()) {
String elem = entry.getKey();
ConfigValue v = entry.getValue();
Path path = Path.newKey(elem);
if (parent != null)
path = path.prepend(parent);
if (v instanceof AbstractConfigObject) {
findPaths(entries, path, (AbstractConfigObject) v);
} else if (v instanceof ConfigNull) {
// nothing; nulls are conceptually not in a Config
} else {
entries.add(new AbstractMap.SimpleImmutableEntry<String, ConfigValue>(path.render(), v));
}
}
}
@Override
public Set<Map.Entry<String, ConfigValue>> entrySet() {
Set<Map.Entry<String, ConfigValue>> entries = new HashSet<Map.Entry<String, ConfigValue>>();
findPaths(entries, null, object);
return entries;
}
static private AbstractConfigValue findKey(AbstractConfigObject self, String key,
ConfigValueType expected, Path originalPath) {
AbstractConfigValue v = self.peekAssumingResolved(key, originalPath);
if (v == null)
throw new Missing(originalPath.render());
if (expected != null)
v = DefaultTransformer.transform(v, expected);
if (v.valueType() == ConfigValueType.NULL)
throw new Null(v.origin(), originalPath.render(),
expected != null ? expected.name() : null);
else if (expected != null && v.valueType() != expected)
throw new WrongType(v.origin(), originalPath.render(), expected.name(),
v.valueType().name());
else
return v;
}
static private AbstractConfigValue find(AbstractConfigObject self, Path path,
ConfigValueType expected, Path originalPath) {
try {
String key = path.first();
Path next = path.remainder();
if (next == null) {
return findKey(self, key, expected, originalPath);
} else {
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
ConfigValueType.OBJECT,
originalPath.subPath(0, originalPath.length() - next.length()));
assert (o != null); // missing was supposed to throw
return find(o, next, expected, originalPath);
}
} catch (NotResolved e) {
throw ConfigImpl.improveNotResolved(path, e);
}
}
AbstractConfigValue find(Path pathExpression, ConfigValueType expected, Path originalPath) {
return find(object, pathExpression, expected, originalPath);
}
AbstractConfigValue find(String pathExpression, ConfigValueType expected) {
Path path = Path.newPath(pathExpression);
return find(path, expected, path);
}
@Override
public AbstractConfigValue getValue(String path) {
return find(path, null);
}
@Override
public boolean getBoolean(String path) {
ConfigValue v = find(path, ConfigValueType.BOOLEAN);
return (Boolean) v.unwrapped();
}
private ConfigNumber getConfigNumber(String path) {
ConfigValue v = find(path, ConfigValueType.NUMBER);
return (ConfigNumber) v;
}
@Override
public Number getNumber(String path) {
return getConfigNumber(path).unwrapped();
}
@Override
public int getInt(String path) {
ConfigNumber n = getConfigNumber(path);
return n.intValueRangeChecked(path);
}
@Override
public long getLong(String path) {
return getNumber(path).longValue();
}
@Override
public double getDouble(String path) {
return getNumber(path).doubleValue();
}
@Override
public String getString(String path) {
ConfigValue v = find(path, ConfigValueType.STRING);
return (String) v.unwrapped();
}
@Override
public ConfigList getList(String path) {
AbstractConfigValue v = find(path, ConfigValueType.LIST);
return (ConfigList) v;
}
@Override
public AbstractConfigObject getObject(String path) {
AbstractConfigObject obj = (AbstractConfigObject) find(path, ConfigValueType.OBJECT);
return obj;
}
@Override
public SimpleConfig getConfig(String path) {
return getObject(path).toConfig();
}
@Override
public Object getAnyRef(String path) {
ConfigValue v = find(path, null);
return v.unwrapped();
}
@Override
public Long getBytes(String path) {
Long size = null;
try {
size = getLong(path);
} catch (WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING);
size = parseBytes((String) v.unwrapped(),
v.origin(), path);
}
return size;
}
@Deprecated
@Override
public Long getMilliseconds(String path) {
return getDuration(path, TimeUnit.MILLISECONDS);
}
@Deprecated
@Override
public Long getNanoseconds(String path) {
return getDuration(path, TimeUnit.NANOSECONDS);
}
@Override
public Long getDuration(String path, TimeUnit unit) {
Long result = null;
try {
result = unit.convert(getLong(path), TimeUnit.MILLISECONDS);
} catch (WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING);
result = unit.convert(
parseDuration((String) v.unwrapped(), v.origin(), path),
TimeUnit.NANOSECONDS);
}
return result;
}
@SuppressWarnings("unchecked")
private <T> List<T> getHomogeneousUnwrappedList(String path,
ConfigValueType expected) {
List<T> l = new ArrayList<T>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue cv : list) {
// variance would be nice, but stupid cast will do
AbstractConfigValue v = (AbstractConfigValue) cv;
if (expected != null) {
v = DefaultTransformer.transform(v, expected);
}
if (v.valueType() != expected)
throw new WrongType(v.origin(), path,
"list of " + expected.name(), "list of "
+ v.valueType().name());
l.add((T) v.unwrapped());
}
return l;
}
@Override
public List<Boolean> getBooleanList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.BOOLEAN);
}
@Override
public List<Number> getNumberList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.NUMBER);
}
@Override
public List<Integer> getIntList(String path) {
List<Integer> l = new ArrayList<Integer>();
List<AbstractConfigValue> numbers = getHomogeneousWrappedList(path, ConfigValueType.NUMBER);
for (AbstractConfigValue v : numbers) {
l.add(((ConfigNumber) v).intValueRangeChecked(path));
}
return l;
}
@Override
public List<Long> getLongList(String path) {
List<Long> l = new ArrayList<Long>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.longValue());
}
return l;
}
@Override
public List<Double> getDoubleList(String path) {
List<Double> l = new ArrayList<Double>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.doubleValue());
}
return l;
}
@Override
public List<String> getStringList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.STRING);
}
@SuppressWarnings("unchecked")
private <T extends ConfigValue> List<T> getHomogeneousWrappedList(
String path, ConfigValueType expected) {
List<T> l = new ArrayList<T>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue cv : list) {
// variance would be nice, but stupid cast will do
AbstractConfigValue v = (AbstractConfigValue) cv;
if (expected != null) {
v = DefaultTransformer.transform(v, expected);
}
if (v.valueType() != expected)
throw new WrongType(v.origin(), path,
"list of " + expected.name(), "list of "
+ v.valueType().name());
l.add((T) v);
}
return l;
}
@Override
public List<ConfigObject> getObjectList(String path) {
return getHomogeneousWrappedList(path, ConfigValueType.OBJECT);
}
@Override
public List<? extends Config> getConfigList(String path) {
List<ConfigObject> objects = getObjectList(path);
List<Config> l = new ArrayList<Config>();
for (ConfigObject o : objects) {
l.add(o.toConfig());
}
return l;
}
@Override
public List<? extends Object> getAnyRefList(String path) {
List<Object> l = new ArrayList<Object>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
l.add(v.unwrapped());
}
return l;
}
@Override
public List<Long> getBytesList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = parseBytes(s, v.origin(), path);
l.add(n);
} else {
throw new WrongType(v.origin(), path,
"memory size string or number of bytes", v.valueType()
.name());
}
}
return l;
}
@Override
public List<Long> getDurationList(String path, TimeUnit unit) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
Long n = unit.convert(
((Number) v.unwrapped()).longValue(),
TimeUnit.MILLISECONDS);
l.add(n);
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = unit.convert(
parseDuration(s, v.origin(), path),
TimeUnit.NANOSECONDS);
l.add(n);
} else {
throw new WrongType(v.origin(), path,
"duration string or number of milliseconds",
v.valueType().name());
}
}
return l;
}
@Deprecated
@Override
public List<Long> getMillisecondsList(String path) {
return getDurationList(path, TimeUnit.MILLISECONDS);
}
@Deprecated
@Override
public List<Long> getNanosecondsList(String path) {
return getDurationList(path, TimeUnit.NANOSECONDS);
}
@Override
public AbstractConfigObject toFallbackValue() {
return object;
}
@Override
public SimpleConfig withFallback(ConfigMergeable other) {
// this can return "this" if the withFallback doesn't need a new
// ConfigObject
return object.withFallback(other).toConfig();
}
@Override
public final boolean equals(Object other) {
if (other instanceof SimpleConfig) {
return object.equals(((SimpleConfig) other).object);
} else {
return false;
}
}
@Override
public final int hashCode() {
// we do the "41*" just so our hash code won't match that of the
// underlying object. there's no real reason it can't match, but
// making it not match might catch some kinds of bug.
return 41 * object.hashCode();
}
@Override
public String toString() {
return "Config(" + object.toString() + ")";
}
private static String getUnits(String s) {
int i = s.length() - 1;
while (i >= 0) {
char c = s.charAt(i);
if (!Character.isLetter(c))
break;
i -= 1;
}
return s.substring(i + 1);
}
/**
* Parses a duration string. If no units are specified in the string, it is
* assumed to be in milliseconds. The returned duration is in nanoseconds.
* The purpose of this function is to implement the duration-related methods
* in the ConfigObject interface.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return duration in nanoseconds
* @throws ConfigException
* if string is invalid
*/
public static long parseDuration(String input,
ConfigOrigin originForException, String pathForException) {
String s = ConfigImplUtil.unicodeTrim(input);
String originalUnitString = getUnits(s);
String unitString = originalUnitString;
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length()
- unitString.length()));
TimeUnit units = null;
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in duration value '" + input
+ "'");
if (unitString.length() > 2 && !unitString.endsWith("s"))
unitString = unitString + "s";
// note that this is deliberately case-sensitive
if (unitString.equals("") || unitString.equals("ms")
|| unitString.equals("milliseconds")) {
units = TimeUnit.MILLISECONDS;
} else if (unitString.equals("us") || unitString.equals("microseconds")) {
units = TimeUnit.MICROSECONDS;
} else if (unitString.equals("ns") || unitString.equals("nanoseconds")) {
units = TimeUnit.NANOSECONDS;
} else if (unitString.equals("d") || unitString.equals("days")) {
units = TimeUnit.DAYS;
} else if (unitString.equals("h") || unitString.equals("hours")) {
units = TimeUnit.HOURS;
} else if (unitString.equals("s") || unitString.equals("seconds")) {
units = TimeUnit.SECONDS;
} else if (unitString.equals("m") || unitString.equals("minutes")) {
units = TimeUnit.MINUTES;
} else {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse time unit '"
+ originalUnitString
+ "' (try ns, us, ms, s, m, d)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// possible precision loss;
// otherwise as a double.
if (numberString.matches("[0-9]+")) {
return units.toNanos(Long.parseLong(numberString));
} else {
long nanosInUnit = units.toNanos(1);
return (long) (Double.parseDouble(numberString) * nanosInUnit);
}
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse duration number '"
+ numberString + "'");
}
}
private static enum MemoryUnit {
BYTES("", 1024, 0),
KILOBYTES("kilo", 1000, 1),
MEGABYTES("mega", 1000, 2),
GIGABYTES("giga", 1000, 3),
TERABYTES("tera", 1000, 4),
PETABYTES("peta", 1000, 5),
EXABYTES("exa", 1000, 6),
ZETTABYTES("zetta", 1000, 7),
YOTTABYTES("yotta", 1000, 8),
KIBIBYTES("kibi", 1024, 1),
MEBIBYTES("mebi", 1024, 2),
GIBIBYTES("gibi", 1024, 3),
TEBIBYTES("tebi", 1024, 4),
PEBIBYTES("pebi", 1024, 5),
EXBIBYTES("exbi", 1024, 6),
ZEBIBYTES("zebi", 1024, 7),
YOBIBYTES("yobi", 1024, 8);
final String prefix;
final int powerOf;
final int power;
final long bytes;
MemoryUnit(String prefix, int powerOf, int power) {
this.prefix = prefix;
this.powerOf = powerOf;
this.power = power;
int i = power;
long bytes = 1;
while (i > 0) {
bytes *= powerOf;
--i;
}
this.bytes = bytes;
}
private static Map<String, MemoryUnit> makeUnitsMap() {
Map<String, MemoryUnit> map = new HashMap<String, MemoryUnit>();
for (MemoryUnit unit : MemoryUnit.values()) {
map.put(unit.prefix + "byte", unit);
map.put(unit.prefix + "bytes", unit);
if (unit.prefix.length() == 0) {
map.put("b", unit);
map.put("B", unit);
map.put("", unit); // no unit specified means bytes
} else {
String first = unit.prefix.substring(0, 1);
String firstUpper = first.toUpperCase();
if (unit.powerOf == 1024) {
map.put(first, unit); // 512m
map.put(firstUpper, unit); // 512M
map.put(firstUpper + "i", unit); // 512Mi
map.put(firstUpper + "iB", unit); // 512MiB
} else if (unit.powerOf == 1000) {
if (unit.power == 1) {
map.put(first + "B", unit); // 512kB
} else {
map.put(firstUpper + "B", unit); // 512MB
}
} else {
throw new RuntimeException("broken MemoryUnit enum");
}
}
}
return map;
}
private static Map<String, MemoryUnit> unitsMap = makeUnitsMap();
static MemoryUnit parseUnit(String unit) {
return unitsMap.get(unit);
}
}
/**
* Parses a size-in-bytes string. If no units are specified in the string,
* it is assumed to be in bytes. The returned value is in bytes. The purpose
* of this function is to implement the size-in-bytes-related methods in the
* Config interface.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return size in bytes
* @throws ConfigException
* if string is invalid
*/
public static long parseBytes(String input, ConfigOrigin originForException,
String pathForException) {
String s = ConfigImplUtil.unicodeTrim(input);
String unitString = getUnits(s);
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0,
s.length() - unitString.length()));
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in size-in-bytes value '"
+ input + "'");
MemoryUnit units = MemoryUnit.parseUnit(unitString);
if (units == null) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes unit '" + unitString
+ "' (try k, K, kB, KiB, kilobytes, kibibytes)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// possible precision loss; otherwise as a double.
if (numberString.matches("[0-9]+")) {
return Long.parseLong(numberString) * units.bytes;
} else {
return (long) (Double.parseDouble(numberString) * units.bytes);
}
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes number '" + numberString + "'");
}
}
private AbstractConfigValue peekPath(Path path) {
return root().peekPath(path);
}
private static void addProblem(List<ConfigException.ValidationProblem> accumulator, Path path,
ConfigOrigin origin, String problem) {
accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem));
}
private static String getDesc(ConfigValue refValue) {
if (refValue instanceof AbstractConfigObject) {
AbstractConfigObject obj = (AbstractConfigObject) refValue;
if (obj.isEmpty())
return "object";
else
return "object with keys " + obj.keySet();
} else if (refValue instanceof SimpleConfigList) {
return "list";
} else {
return refValue.valueType().name().toLowerCase();
}
}
private static void addMissing(List<ConfigException.ValidationProblem> accumulator,
ConfigValue refValue, Path path, ConfigOrigin origin) {
addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: "
+ getDesc(refValue));
}
private static void addWrongType(List<ConfigException.ValidationProblem> accumulator,
ConfigValue refValue, AbstractConfigValue actual, Path path) {
addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render()
+ "', expecting: " + getDesc(refValue) + " but got: "
+ getDesc(actual));
}
private static boolean couldBeNull(AbstractConfigValue v) {
return DefaultTransformer.transform(v, ConfigValueType.NULL)
.valueType() == ConfigValueType.NULL;
}
private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) {
if (couldBeNull((AbstractConfigValue) reference) || couldBeNull(value)) {
// we allow any setting to be null
return true;
} else if (reference instanceof AbstractConfigObject) {
if (value instanceof AbstractConfigObject) {
return true;
} else {
return false;
}
} else if (reference instanceof SimpleConfigList) {
if (value instanceof SimpleConfigList) {
return true;
} else {
return false;
}
} else if (reference instanceof ConfigString) {
// assume a string could be gotten as any non-collection type;
// allows things like getMilliseconds including domain-specific
// interpretations of strings
return true;
} else if (value instanceof ConfigString) {
// assume a string could be gotten as any non-collection type
return true;
} else {
if (reference.valueType() == value.valueType()) {
return true;
} else {
return false;
}
}
}
// path is null if we're at the root
private static void checkValidObject(Path path, AbstractConfigObject reference,
AbstractConfigObject value,
List<ConfigException.ValidationProblem> accumulator) {
for (Map.Entry<String, ConfigValue> entry : reference.entrySet()) {
String key = entry.getKey();
Path childPath;
if (path != null)
childPath = Path.newKey(key).prepend(path);
else
childPath = Path.newKey(key);
AbstractConfigValue v = value.get(key);
if (v == null) {
addMissing(accumulator, entry.getValue(), childPath, value.origin());
} else {
checkValid(childPath, entry.getValue(), v, accumulator);
}
}
}
private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value,
List<ConfigException.ValidationProblem> accumulator) {
// Unmergeable is supposed to be impossible to encounter in here
// because we check for resolve status up front.
if (haveCompatibleTypes(reference, value)) {
if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) {
checkValidObject(path, (AbstractConfigObject) reference,
(AbstractConfigObject) value, accumulator);
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) {
SimpleConfigList listRef = (SimpleConfigList) reference;
SimpleConfigList listValue = (SimpleConfigList) value;
if (listRef.isEmpty() || listValue.isEmpty()) {
// can't verify type, leave alone
} else {
AbstractConfigValue refElement = listRef.get(0);
for (ConfigValue elem : listValue) {
AbstractConfigValue e = (AbstractConfigValue) elem;
if (!haveCompatibleTypes(refElement, e)) {
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
+ "' contains wrong value type, expecting list of "
+ getDesc(refElement) + " but got element of type "
+ getDesc(e));
// don't add a problem for every last array element
break;
}
}
}
}
} else {
addWrongType(accumulator, reference, value, path);
}
}
@Override
public void checkValid(Config reference, String... restrictToPaths) {
SimpleConfig ref = (SimpleConfig) reference;
// unresolved reference typesafe is a bug in the caller of checkValid
if (ref.root().resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"do not call checkValid() with an unresolved reference typesafe, call Config#resolve(), see Config#resolve() API docs");
// unresolved typesafe under validation is a bug in something,
// NotResolved is a more specific subclass of BugOrBroken
if (root().resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.NotResolved(
"need to Config#resolve() each typesafe before using it, see the API docs for Config#resolve()");
// Now we know that both reference and this typesafe are resolved
List<ConfigException.ValidationProblem> problems = new ArrayList<ConfigException.ValidationProblem>();
if (restrictToPaths.length == 0) {
checkValidObject(null, ref.root(), root(), problems);
} else {
for (String p : restrictToPaths) {
Path path = Path.newPath(p);
AbstractConfigValue refValue = ref.peekPath(path);
if (refValue != null) {
AbstractConfigValue child = peekPath(path);
if (child != null) {
checkValid(path, refValue, child, problems);
} else {
addMissing(problems, refValue, path, origin());
}
}
}
}
if (!problems.isEmpty()) {
throw new ConfigException.ValidationFailed(problems);
}
}
@Override
public SimpleConfig withOnlyPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
return new SimpleConfig(root().withOnlyPath(path));
}
@Override
public SimpleConfig withoutPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
return new SimpleConfig(root().withoutPath(path));
}
@Override
public SimpleConfig withValue(String pathExpression, ConfigValue v) {
Path path = Path.newPath(pathExpression);
return new SimpleConfig(root().withValue(path, v));
}
SimpleConfig atKey(ConfigOrigin origin, String key) {
return root().atKey(origin, key);
}
@Override
public SimpleConfig atKey(String key) {
return root().atKey(key);
}
@Override
public Config atPath(String path) {
return root().atPath(path);
}
// serialization list goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}