in scripts/src/main/java/com/gu/typesafe/config/impl/PropertiesParser.java [98:209]
private static AbstractConfigObject fromPathMap(ConfigOrigin origin,
Map<Path, Object> pathMap, boolean convertedFromProperties) {
/*
* First, build a list of paths that will have values, either string or
* object values.
*/
Set<Path> scopePaths = new HashSet<Path>();
Set<Path> valuePaths = new HashSet<Path>();
for (Path path : pathMap.keySet()) {
// add value's path
valuePaths.add(path);
// all parent paths are objects
Path next = path.parent();
while (next != null) {
scopePaths.add(next);
next = next.parent();
}
}
if (convertedFromProperties) {
/*
* If any string values are also objects containing other values,
* drop those string values - objects "win".
*/
valuePaths.removeAll(scopePaths);
} else {
/* If we didn't start out as properties, then this is an error. */
for (Path path : valuePaths) {
if (scopePaths.contains(path)) {
throw new ConfigException.BugOrBroken(
"In the map, path '"
+ path.render()
+ "' occurs as both the parent object of a value and as a value. "
+ "Because Map has no defined ordering, this is a broken situation.");
}
}
}
/*
* Create maps for the object-valued values.
*/
Map<String, com.gu.typesafe.config.impl.AbstractConfigValue> root = new HashMap<String, com.gu.typesafe.config.impl.AbstractConfigValue>();
Map<Path, Map<String, com.gu.typesafe.config.impl.AbstractConfigValue>> scopes = new HashMap<Path, Map<String, com.gu.typesafe.config.impl.AbstractConfigValue>>();
for (Path path : scopePaths) {
Map<String, com.gu.typesafe.config.impl.AbstractConfigValue> scope = new HashMap<String, com.gu.typesafe.config.impl.AbstractConfigValue>();
scopes.put(path, scope);
}
/* Store string values in the associated scope maps */
for (Path path : valuePaths) {
Path parentPath = path.parent();
Map<String, com.gu.typesafe.config.impl.AbstractConfigValue> parent = parentPath != null ? scopes
.get(parentPath) : root;
String last = path.last();
Object rawValue = pathMap.get(path);
com.gu.typesafe.config.impl.AbstractConfigValue value;
if (convertedFromProperties) {
if (rawValue instanceof String) {
value = new com.gu.typesafe.config.impl.ConfigString.Quoted(origin, (String) rawValue);
} else {
// silently ignore non-string values in Properties
value = null;
}
} else {
value = ConfigImpl.fromAnyRef(pathMap.get(path), origin,
FromMapMode.KEYS_ARE_PATHS);
}
if (value != null)
parent.put(last, value);
}
/*
* Make a list of scope paths from longest to shortest, so children go
* before parents.
*/
List<Path> sortedScopePaths = new ArrayList<Path>();
sortedScopePaths.addAll(scopePaths);
// sort descending by length
Collections.sort(sortedScopePaths, new Comparator<Path>() {
@Override
public int compare(Path a, Path b) {
// Path.length() is O(n) so in theory this sucks
// but in practice we can make Path precompute length
// if it ever matters.
return b.length() - a.length();
}
});
/*
* Create ConfigObject for each scope map, working from children to
* parents to avoid modifying any already-created ConfigObject. This is
* where we need the sorted list.
*/
for (Path scopePath : sortedScopePaths) {
Map<String, com.gu.typesafe.config.impl.AbstractConfigValue> scope = scopes.get(scopePath);
Path parentPath = scopePath.parent();
Map<String, com.gu.typesafe.config.impl.AbstractConfigValue> parent = parentPath != null ? scopes
.get(parentPath) : root;
AbstractConfigObject o = new com.gu.typesafe.config.impl.SimpleConfigObject(origin, scope,
ResolveStatus.RESOLVED, false /* ignoresFallbacks */);
parent.put(scopePath.last(), o);
}
// return root config object
return new com.gu.typesafe.config.impl.SimpleConfigObject(origin, root, ResolveStatus.RESOLVED,
false /* ignoresFallbacks */);
}