• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.1
today-context/src/main/java/infra/context/properties/bind/MapBinder.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.properties.bind;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.Collection;
23
import java.util.EnumMap;
24
import java.util.Map;
25
import java.util.Properties;
26
import java.util.function.Supplier;
27

28
import infra.context.properties.bind.Binder.Context;
29
import infra.context.properties.source.ConfigurationProperty;
30
import infra.context.properties.source.ConfigurationPropertyName;
31
import infra.context.properties.source.ConfigurationPropertyName.Form;
32
import infra.context.properties.source.ConfigurationPropertySource;
33
import infra.context.properties.source.ConfigurationPropertyState;
34
import infra.context.properties.source.IterableConfigurationPropertySource;
35
import infra.core.ResolvableType;
36
import infra.util.CollectionUtils;
37

38
/**
39
 * {@link AggregateBinder} for Maps.
40
 *
41
 * @author Phillip Webb
42
 * @author Madhura Bhave
43
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
44
 * @since 4.0
45
 */
46
class MapBinder extends AggregateBinder<Map<Object, Object>> {
47

48
  private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);
5✔
49

50
  MapBinder(Context context) {
51
    super(context);
3✔
52
  }
1✔
53

54
  @Override
55
  protected boolean isAllowRecursiveBinding(@Nullable ConfigurationPropertySource source) {
56
    return true;
2✔
57
  }
58

59
  @Nullable
60
  @Override
61
  protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder) {
62
    Bindable<?> resolvedTarget = resolveTarget(target);
4✔
63

64
    if (!hasDescendants(name) && !ConfigurationPropertyName.EMPTY.equals(name)) {
8!
65
      for (ConfigurationPropertySource source : context.getSources()) {
12✔
66
        ConfigurationProperty property = source.getConfigurationProperty(name);
4✔
67
        if (property != null) {
2✔
68
          context.setConfigurationProperty(property);
4✔
69
          Object result = context.getPlaceholdersResolver().resolvePlaceholders(property.getValue());
7✔
70
          return context.getConverter().convert(result, target);
7✔
71
        }
72
      }
1✔
73
    }
74

75
    Map<Object, Object> map = createMap(target);
4✔
76
    for (ConfigurationPropertySource source : context.getSources()) {
12✔
77
      if (!ConfigurationPropertyName.EMPTY.equals(name)) {
4✔
78
        source = source.filter(name::isAncestorOf);
8✔
79
      }
80
      new EntryBinder(name, resolvedTarget, elementBinder).bindEntries(source, map);
10✔
81
    }
1✔
82
    return map.isEmpty() ? null : map;
7✔
83
  }
84

85
  private Map<Object, Object> createMap(Bindable<?> target) {
86
    Class<?> mapType = (target.getValue() != null) ? Map.class : target.getType().resolve(Object.class);
10✔
87
    if (EnumMap.class.isAssignableFrom(mapType)) {
4✔
88
      Class<?> keyType = target.getType().asMap().resolveGeneric(0);
11✔
89
      return CollectionUtils.createMap(mapType, keyType, 0);
5✔
90
    }
91
    return CollectionUtils.createMap(mapType, 0);
4✔
92
  }
93

94
  private boolean hasDescendants(ConfigurationPropertyName name) {
95
    for (ConfigurationPropertySource source : context.getSources()) {
12✔
96
      if (source.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT) {
5✔
97
        return true;
2✔
98
      }
99
    }
1✔
100
    return false;
2✔
101
  }
102

103
  private Bindable<?> resolveTarget(Bindable<?> target) {
104
    Class<?> type = target.getType().resolve(Object.class);
5✔
105
    if (Properties.class.isAssignableFrom(type)) {
4✔
106
      return STRING_STRING_MAP;
2✔
107
    }
108
    return target;
2✔
109
  }
110

111
  @Override
112
  protected Map<Object, Object> merge(Supplier<Map<Object, Object>> existing, Map<Object, Object> additional) {
113
    Map<Object, Object> existingMap = getExistingIfPossible(existing);
4✔
114
    if (existingMap == null) {
2✔
115
      return additional;
2✔
116
    }
117
    try {
118
      existingMap.putAll(additional);
3✔
119
      return copyIfPossible(existingMap);
4✔
120
    }
121
    catch (UnsupportedOperationException ex) {
1✔
122
      Map<Object, Object> result = createNewMap(additional.getClass(), existingMap);
6✔
123
      result.putAll(additional);
3✔
124
      return result;
2✔
125
    }
126
  }
127

128
  @Nullable
129
  private Map<Object, Object> getExistingIfPossible(Supplier<Map<Object, Object>> existing) {
130
    try {
131
      return existing.get();
4✔
132
    }
133
    catch (Exception ex) {
1✔
134
      return null;
2✔
135
    }
136
  }
137

138
  private Map<Object, Object> copyIfPossible(Map<Object, Object> map) {
139
    try {
140
      return createNewMap(map.getClass(), map);
6✔
141
    }
142
    catch (Exception ex) {
1✔
143
      return map;
2✔
144
    }
145
  }
146

147
  private Map<Object, Object> createNewMap(Class<?> mapClass, Map<Object, Object> map) {
148
    Map<Object, Object> result = CollectionUtils.createMap(mapClass, map.size());
5✔
149
    result.putAll(map);
3✔
150
    return result;
2✔
151
  }
152

153
  private class EntryBinder {
154

155
    private final ConfigurationPropertyName root;
156

157
    private final AggregateElementBinder elementBinder;
158

159
    private final ResolvableType mapType;
160

161
    private final ResolvableType keyType;
162

163
    private final ResolvableType valueType;
164

165
    private final Class<?> resolvedValueType;
166

167
    private final boolean valueTreatedAsNestedMap;
168

169
    private final Bindable<Object> bindableMapType;
170

171
    private final Bindable<Object> bindableValueType;
172

173
    EntryBinder(ConfigurationPropertyName root, Bindable<?> target, AggregateElementBinder elementBinder) {
5✔
174
      this.root = root;
3✔
175
      this.elementBinder = elementBinder;
3✔
176
      this.mapType = target.getType().asMap();
5✔
177
      this.keyType = this.mapType.getGeneric(0);
11✔
178
      this.valueType = this.mapType.getGeneric(1);
11✔
179
      this.resolvedValueType = this.valueType.resolve(Object.class);
6✔
180
      this.valueTreatedAsNestedMap = Object.class.equals(this.resolvedValueType);
6✔
181
      this.bindableMapType = Bindable.of(this.mapType);
5✔
182
      this.bindableValueType = Bindable.of(this.valueType);
5✔
183
    }
1✔
184

185
    void bindEntries(ConfigurationPropertySource source, Map<Object, Object> map) {
186
      if (source instanceof IterableConfigurationPropertySource is) {
6✔
187
        BindConverter converter = context.getConverter();
5✔
188
        for (ConfigurationPropertyName name : is) {
10✔
189
          ConfigurationPropertyName entryName = getEntryName(source, name);
5✔
190
          Object key = converter.convert(getKeyName(entryName), this.keyType);
10✔
191
          Bindable<?> valueBindable = getValueBindable(name);
4✔
192
          map.computeIfAbsent(key, k -> this.elementBinder.bind(entryName, valueBindable));
14✔
193
        }
1✔
194
      }
195
    }
1✔
196

197
    private Bindable<?> getValueBindable(ConfigurationPropertyName name) {
198
      return (!isParentOf(name) && this.valueTreatedAsNestedMap) ? this.bindableMapType : this.bindableValueType;
13✔
199
    }
200

201
    private ConfigurationPropertyName getEntryName(ConfigurationPropertySource source, ConfigurationPropertyName name) {
202
      if (Collection.class.isAssignableFrom(this.resolvedValueType) || this.valueType.isArray()) {
9✔
203
        return chopNameAtNumericIndex(name);
4✔
204
      }
205
      if (!isParentOf(name) && (this.valueTreatedAsNestedMap || !isScalarValue(source, name))) {
12✔
206
        return name.chop(this.root.getNumberOfElements() + 1);
8✔
207
      }
208
      return name;
2✔
209
    }
210

211
    private boolean isParentOf(ConfigurationPropertyName name) {
212
      return this.root.isParentOf(name);
5✔
213
    }
214

215
    private ConfigurationPropertyName chopNameAtNumericIndex(ConfigurationPropertyName name) {
216
      int start = this.root.getNumberOfElements() + 1;
6✔
217
      int size = name.getNumberOfElements();
3✔
218
      for (int i = start; i < size; i++) {
7✔
219
        if (name.isNumericIndex(i)) {
4✔
220
          return name.chop(i);
4✔
221
        }
222
      }
223
      return name;
2✔
224
    }
225

226
    private boolean isScalarValue(ConfigurationPropertySource source, ConfigurationPropertyName name) {
227
      Class<?> resolved = this.valueType.resolve(Object.class);
5✔
228
      if (!resolved.getName().startsWith("java.lang") && !resolved.isEnum()) {
8✔
229
        return false;
2✔
230
      }
231
      ConfigurationProperty property = source.getConfigurationProperty(name);
4✔
232
      if (property == null) {
2!
233
        return false;
×
234
      }
235
      Object value = property.getValue();
3✔
236
      value = context.getPlaceholdersResolver().resolvePlaceholders(value);
7✔
237
      return context.getConverter().canConvert(value, this.valueType);
11✔
238
    }
239

240
    private String getKeyName(ConfigurationPropertyName name) {
241
      StringBuilder result = new StringBuilder();
4✔
242
      for (int i = this.root.getNumberOfElements(); i < name.getNumberOfElements(); i++) {
10✔
243
        if (!result.isEmpty()) {
3✔
244
          result.append('.');
4✔
245
        }
246
        result.append(name.getElement(i, Form.ORIGINAL));
7✔
247
      }
248
      return result.toString();
3✔
249
    }
250

251
  }
252

253
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc