MapBinder Class — spring-boot Architecture
Architecture documentation for the MapBinder class in MapBinder.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java lines 43–248
class MapBinder extends AggregateBinder<Map<Object, Object>> {
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);
MapBinder(Context context) {
super(context);
}
@Override
protected boolean isAllowRecursiveBinding(@Nullable ConfigurationPropertySource source) {
return true;
}
@Override
protected @Nullable Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder elementBinder) {
Bindable<?> resolvedTarget = resolveTarget(target);
boolean hasDescendants = hasDescendants(name);
if (!hasDescendants && !ConfigurationPropertyName.EMPTY.equals(name)) {
for (ConfigurationPropertySource source : getContext().getSources()) {
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property != null) {
getContext().setConfigurationProperty(property);
Object result = getContext().getPlaceholdersResolver().resolvePlaceholders(property.getValue());
return getContext().getConverter().convert(result, target);
}
}
}
Map<Object, Object> map = createMap(target);
for (ConfigurationPropertySource source : getContext().getSources()) {
if (!ConfigurationPropertyName.EMPTY.equals(name)) {
source = source.filter(name::isAncestorOf);
}
new EntryBinder(name, resolvedTarget, elementBinder).bindEntries(source, map);
}
return map.isEmpty() ? null : map;
}
private Map<Object, Object> createMap(Bindable<?> target) {
Class<?> mapType = (target.getValue() != null) ? Map.class : target.getType().resolve(Object.class);
if (EnumMap.class.isAssignableFrom(mapType)) {
Class<?> keyType = target.getType().asMap().resolveGeneric(0);
return CollectionFactory.createMap(mapType, keyType, 0);
}
return CollectionFactory.createMap(mapType, 0);
}
private boolean hasDescendants(ConfigurationPropertyName name) {
for (ConfigurationPropertySource source : getContext().getSources()) {
if (source.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT) {
return true;
}
}
return false;
}
private Bindable<?> resolveTarget(Bindable<?> target) {
Class<?> type = target.getType().resolve(Object.class);
if (Properties.class.isAssignableFrom(type)) {
return STRING_STRING_MAP;
}
return target;
}
@Override
protected Map<Object, Object> merge(Supplier<Map<Object, Object>> existing, Map<Object, Object> additional) {
Map<Object, Object> existingMap = getExistingIfPossible(existing);
if (existingMap == null) {
return additional;
}
try {
existingMap.putAll(additional);
return copyIfPossible(existingMap);
}
catch (UnsupportedOperationException ex) {
Map<Object, Object> result = createNewMap(additional.getClass(), existingMap);
result.putAll(additional);
return result;
}
}
private @Nullable Map<Object, Object> getExistingIfPossible(Supplier<Map<Object, Object>> existing) {
try {
return existing.get();
}
catch (Exception ex) {
return null;
}
}
private Map<Object, Object> copyIfPossible(Map<Object, Object> map) {
try {
return createNewMap(map.getClass(), map);
}
catch (Exception ex) {
return map;
}
}
private Map<Object, Object> createNewMap(Class<?> mapClass, Map<Object, Object> map) {
Map<Object, Object> result = CollectionFactory.createMap(mapClass, map.size());
result.putAll(map);
return result;
}
private class EntryBinder {
private final ConfigurationPropertyName root;
private final AggregateElementBinder elementBinder;
private final ResolvableType mapType;
private final ResolvableType keyType;
private final ResolvableType valueType;
private final Class<?> resolvedValueType;
private final boolean valueTreatedAsNestedMap;
private final Bindable<Object> bindableMapType;
private final Bindable<Object> bindableValueType;
EntryBinder(ConfigurationPropertyName root, Bindable<?> target, AggregateElementBinder elementBinder) {
this.root = root;
this.elementBinder = elementBinder;
this.mapType = target.getType().asMap();
this.keyType = this.mapType.getGeneric(0);
this.valueType = this.mapType.getGeneric(1);
this.resolvedValueType = this.valueType.resolve(Object.class);
this.valueTreatedAsNestedMap = Object.class.equals(this.resolvedValueType);
this.bindableMapType = Bindable.of(this.mapType);
this.bindableValueType = Bindable.of(this.valueType);
}
void bindEntries(ConfigurationPropertySource source, Map<Object, Object> map) {
if (source instanceof IterableConfigurationPropertySource iterableSource) {
for (ConfigurationPropertyName name : iterableSource) {
ConfigurationPropertyName entryName = getEntryName(source, name);
Object key = getContext().getConverter().convert(getKeyName(entryName), this.keyType);
Bindable<?> valueBindable = getValueBindable(name);
map.computeIfAbsent(key, (k) -> this.elementBinder.bind(entryName, valueBindable));
}
}
}
private Bindable<?> getValueBindable(ConfigurationPropertyName name) {
return (!isParentOf(name) && this.valueTreatedAsNestedMap) ? this.bindableMapType : this.bindableValueType;
}
private ConfigurationPropertyName getEntryName(ConfigurationPropertySource source,
ConfigurationPropertyName name) {
if (Collection.class.isAssignableFrom(this.resolvedValueType) || this.valueType.isArray()) {
return chopNameAtNumericIndex(name);
}
if (!isParentOf(name) && (this.valueTreatedAsNestedMap || !isScalarValue(source, name))) {
return name.chop(this.root.getNumberOfElements() + 1);
}
return name;
}
private boolean isParentOf(ConfigurationPropertyName name) {
return this.root.isParentOf(name);
}
private ConfigurationPropertyName chopNameAtNumericIndex(ConfigurationPropertyName name) {
int start = this.root.getNumberOfElements() + 1;
int size = name.getNumberOfElements();
for (int i = start; i < size; i++) {
if (name.isNumericIndex(i)) {
return name.chop(i);
}
}
return name;
}
private boolean isScalarValue(ConfigurationPropertySource source, ConfigurationPropertyName name) {
Class<?> resolved = this.valueType.resolve(Object.class);
if (!resolved.getName().startsWith("java.lang") && !resolved.isEnum()) {
return false;
}
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property == null) {
return false;
}
Object value = property.getValue();
value = getContext().getPlaceholdersResolver().resolvePlaceholders(value);
return getContext().getConverter().canConvert(value, this.valueType);
}
private String getKeyName(ConfigurationPropertyName name) {
StringBuilder result = new StringBuilder();
for (int i = this.root.getNumberOfElements(); i < name.getNumberOfElements(); i++) {
if (!result.isEmpty()) {
result.append('.');
}
result.append(name.getElement(i, Form.ORIGINAL));
}
return result.toString();
}
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free