String stype = (String) typeValue.unwrapped();
try {
Class<T> normalType = (Class<T>) pluginMap.getClass(stype);
ConfigObject aliasDefaults = pluginMap.aliasDefaults(stype);
ConfigObject fieldValues = configObject.withoutKey(classField).withFallback(aliasDefaults);
CodableClassInfo normalInfo = getOrCreateClassInfo(normalType);
return createAndPopulate(normalInfo, normalType, fieldValues);
} catch (ClassNotFoundException e) {
String helpMessage = Plugins.classNameSuggestions(pluginRegistry, pluginMap, stype);
throw new ConfigException.UnresolvedSubstitution(configObject.origin(), helpMessage, e);
}
}
/* if no chance of instantiating current type, try to get a new type from various special syntax/ settings */
if ((type == null) || Modifier.isAbstract(type.getModifiers()) || Modifier.isInterface(type.getModifiers())) {
/* "type-value : {...}" syntax; ie. if there is only one key, see if it would be a valid type */
if (configObject.size() == 1) {
String singleKeyName = configObject.keySet().iterator().next();
try {
Class<T> singleKeyType = (Class<T>) pluginMap.getClass(singleKeyName);
CodableClassInfo singleKeyInfo = getOrCreateClassInfo(singleKeyType);
ConfigObject aliasDefaults = pluginMap.aliasDefaults(singleKeyName);
ConfigValue configValue = configObject.get(singleKeyName);
if (configValue.valueType() != ConfigValueType.OBJECT) {
if (aliasDefaults.get("_primary") != null) {
// if value is not an object, try supporting _primary syntax to derive one
configValue = configValue.atPath((String) aliasDefaults.get("_primary").unwrapped()).root();
} else if (ValueCodable.class.isAssignableFrom(singleKeyType)) {
// see if the resolved type is innately okay with non-objects
try {
T objectShell = singleKeyType.newInstance();
Config fieldDefaults = singleKeyInfo.getFieldDefaults();
// do not merge objects between global defaults and user defaults (incl. alias defaults)
ConfigObject mergedDefaults = aliasDefaults;
for (Map.Entry<String, ConfigValue> pair : fieldDefaults.entrySet()) {
if (!mergedDefaults.containsKey(pair.getKey())) {
mergedDefaults = mergedDefaults.withValue(pair.getKey(), pair.getValue());
}
}
((ValueCodable) objectShell).fromConfigValue(configValue, mergedDefaults);
return objectShell;
} catch (InstantiationException | IllegalAccessException | RuntimeException ex) {
throw new ConfigException.BadValue(configValue.origin(), singleKeyType.getName(),
"exception during instantiation of a ValueCodable", ex);
}
} else {
throw new ConfigException.WrongType(configValue.origin(), singleKeyName,
"OBJECT", configValue.valueType().toString());
}
}
ConfigObject fieldValues = ((ConfigObject) configValue).withFallback(aliasDefaults);
return createAndPopulate(singleKeyInfo, singleKeyType, fieldValues);
} catch (ClassNotFoundException ignored) {
// expected when the single key is not a valid alias or class. could avoid exception if we dropped
// support for single-keys that are just classes (ie. anonymous aliases), but we'll leave it in
// until we have some, more concrete, reason to remove it.
}
}
/* inlined types syntax ie "{ type-value: some-value, some-field: some-other-value, ...}".
* Opt-in is on a per alias basis, and the target type must be unambiguous amongst aliases
* that have opted in. The recognized alias label is then replaced with the _primary field. */
String matched = null;
for (String alias : pluginMap.inlinedAliases()) {
if (configObject.get(alias) != null) {
if (matched != null) {
String message = String.format(
"no type specified, more than one key, and both %s and %s match for inlined types.",
matched, alias);
throw new ConfigException.Parse(configObject.origin(), message);
}
matched = alias;
}
}
if (matched != null) {
Class<T> inlinedType = (Class<T>) pluginMap.getClassIfConfigured(matched);
assert inlinedType != null : "matched is always a key from the pluginMap's inlinedAliases set";
CodableClassInfo inlinedInfo = getOrCreateClassInfo(inlinedType);
ConfigObject aliasDefaults = pluginMap.aliasDefaults(matched);
ConfigValue configValue = configObject.get(matched);
String primaryField = (String) aliasDefaults.get("_primary").unwrapped();
ConfigObject fieldValues = configObject.toConfig().withValue(primaryField, configValue).root()
.withoutKey(matched)
.withFallback(aliasDefaults);
return createAndPopulate(inlinedInfo, inlinedType, fieldValues);
}
/* lastly, check for a _default type. */
Class<T> defaultType = (Class<T>) pluginMap.defaultSugar();
if (defaultType != null) {
CodableClassInfo defaultInfo = getOrCreateClassInfo(defaultType);
ConfigObject aliasDefaults = pluginMap.aliasDefaults("_default");
ConfigObject fieldValues = configObject.withFallback(aliasDefaults);
return createAndPopulate(defaultInfo, defaultType, fieldValues);
}