All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.springframework.boot.context.properties.bind.MapBinder Maven / Gradle / Ivy
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.function.Supplier;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertyState;
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
import org.springframework.core.CollectionFactory;
import org.springframework.core.ResolvableType;
/**
* {@link AggregateBinder} for Maps.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class MapBinder extends AggregateBinder> {
private static final Bindable> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);
MapBinder(Context context) {
super(context);
}
@Override
protected boolean isAllowRecursiveBinding(ConfigurationPropertySource source) {
return true;
}
@Override
protected Object bindAggregate(ConfigurationPropertyName name, Bindable target,
AggregateElementBinder elementBinder) {
Map map = CollectionFactory
.createMap((target.getValue() != null) ? Map.class : target.getType().resolve(Object.class), 0);
Bindable resolvedTarget = resolveTarget(target);
boolean hasDescendants = hasDescendants(name);
for (ConfigurationPropertySource source : getContext().getSources()) {
if (!ConfigurationPropertyName.EMPTY.equals(name)) {
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property != null && !hasDescendants) {
getContext().setConfigurationProperty(property);
Object result = getContext().getPlaceholdersResolver().resolvePlaceholders(property.getValue());
return getContext().getConverter().convert(result, target);
}
source = source.filter(name::isAncestorOf);
}
new EntryBinder(name, resolvedTarget, elementBinder).bindEntries(source, map);
}
return map.isEmpty() ? null : map;
}
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 merge(Supplier> existing, Map additional) {
Map existingMap = getExistingIfPossible(existing);
if (existingMap == null) {
return additional;
}
try {
existingMap.putAll(additional);
return copyIfPossible(existingMap);
}
catch (UnsupportedOperationException ex) {
Map result = createNewMap(additional.getClass(), existingMap);
result.putAll(additional);
return result;
}
}
private Map getExistingIfPossible(Supplier> existing) {
try {
return existing.get();
}
catch (Exception ex) {
return null;
}
}
private Map copyIfPossible(Map map) {
try {
return createNewMap(map.getClass(), map);
}
catch (Exception ex) {
return map;
}
}
private Map createNewMap(Class mapClass, Map map) {
Map 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;
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);
}
void bindEntries(ConfigurationPropertySource source, Map map) {
if (source instanceof IterableConfigurationPropertySource iterableSource) {
for (ConfigurationPropertyName name : iterableSource) {
Bindable valueBindable = getValueBindable(name);
ConfigurationPropertyName entryName = getEntryName(source, name);
Object key = getContext().getConverter().convert(getKeyName(entryName), this.keyType);
map.computeIfAbsent(key, (k) -> this.elementBinder.bind(entryName, valueBindable));
}
}
}
private Bindable getValueBindable(ConfigurationPropertyName name) {
if (!this.root.isParentOf(name) && isValueTreatedAsNestedMap()) {
return Bindable.of(this.mapType);
}
return Bindable.of(this.valueType);
}
private ConfigurationPropertyName getEntryName(ConfigurationPropertySource source,
ConfigurationPropertyName name) {
Class resolved = this.valueType.resolve(Object.class);
if (Collection.class.isAssignableFrom(resolved) || this.valueType.isArray()) {
return chopNameAtNumericIndex(name);
}
if (!this.root.isParentOf(name) && (isValueTreatedAsNestedMap() || !isScalarValue(source, name))) {
return name.chop(this.root.getNumberOfElements() + 1);
}
return 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 isValueTreatedAsNestedMap() {
return Object.class.equals(this.valueType.resolve(Object.class));
}
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();
}
}
}