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

TAKETODAY / today-infrastructure / 15794111114

21 Jun 2025 08:54AM UTC coverage: 81.755% (+0.008%) from 81.747%
15794111114

Pull #286

github

web-flow
Merge e7915e795 into 8470e463d
Pull Request #286: 开发分支

59270 of 77451 branches covered (76.53%)

Branch coverage included in aggregate %.

140365 of 166736 relevant lines covered (84.18%)

3.6 hits per line

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

90.97
today-context/src/main/java/infra/context/properties/source/DefaultIterableConfigurationPropertySource.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.source;
19

20
import java.util.Arrays;
21
import java.util.Collections;
22
import java.util.ConcurrentModificationException;
23
import java.util.HashMap;
24
import java.util.HashSet;
25
import java.util.Iterator;
26
import java.util.LinkedHashMap;
27
import java.util.Map;
28
import java.util.NoSuchElementException;
29
import java.util.Objects;
30
import java.util.Set;
31
import java.util.function.BiPredicate;
32
import java.util.stream.Stream;
33

34
import infra.core.env.EnumerablePropertySource;
35
import infra.core.env.MapPropertySource;
36
import infra.core.env.PropertySource;
37
import infra.core.env.StandardEnvironment;
38
import infra.core.env.SystemEnvironmentPropertySource;
39
import infra.lang.Nullable;
40
import infra.origin.Origin;
41
import infra.origin.OriginLookup;
42
import infra.origin.PropertySourceOrigin;
43
import infra.util.ConcurrentReferenceHashMap;
44

45
/**
46
 * {@link ConfigurationPropertySource} backed by an {@link EnumerablePropertySource}.
47
 * Extends {@link DefaultConfigurationPropertySource} with full "relaxed" mapping support.
48
 * In order to use this adapter the underlying {@link PropertySource} must be fully
49
 * enumerable. A security restricted {@link SystemEnvironmentPropertySource} cannot be
50
 * adapted.
51
 *
52
 * @author Phillip Webb
53
 * @author Madhura Bhave
54
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
55
 * @see PropertyMapper
56
 * @since 4.0
57
 */
58
class DefaultIterableConfigurationPropertySource extends DefaultConfigurationPropertySource
59
        implements IterableConfigurationPropertySource, CachingConfigurationPropertySource {
60

61
  private final BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> ancestorOfCheck;
62

63
  private final SoftReferenceConfigurationPropertyCache<Cache> cache;
64

65
  @Nullable
66
  private volatile ConfigurationPropertyName[] configurationPropertyNames;
67

68
  @Nullable
69
  private final Map<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOfCache;
70

71
  DefaultIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource,
72
          boolean systemEnvironmentSource, PropertyMapper... mappers) {
73
    super(propertySource, systemEnvironmentSource, mappers);
5✔
74
    assertEnumerablePropertySource();
2✔
75
    boolean immutable = isImmutablePropertySource();
3✔
76
    this.ancestorOfCheck = getAncestorOfCheck(mappers);
5✔
77
    this.cache = new SoftReferenceConfigurationPropertyCache<>(immutable);
6✔
78
    this.containsDescendantOfCache = (!systemEnvironmentSource) ? null : new ConcurrentReferenceHashMap<>();
9✔
79
  }
1✔
80

81
  private BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> getAncestorOfCheck(PropertyMapper[] mappers) {
82
    BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> ancestorOfCheck = mappers[0]
3✔
83
            .getAncestorOfCheck();
2✔
84
    for (int i = 1; i < mappers.length; i++) {
8✔
85
      ancestorOfCheck = ancestorOfCheck.or(mappers[i].getAncestorOfCheck());
7✔
86
    }
87
    return ancestorOfCheck;
2✔
88
  }
89

90
  private void assertEnumerablePropertySource() {
91
    if (getPropertySource() instanceof MapPropertySource mapSource) {
9✔
92
      try {
93
        mapSource.getSource().size();
5✔
94
      }
95
      catch (UnsupportedOperationException ex) {
×
96
        throw new IllegalArgumentException("PropertySource must be fully enumerable");
×
97
      }
1✔
98
    }
99
  }
1✔
100

101
  @Override
102
  public ConfigurationPropertyCaching getCaching() {
103
    return this.cache;
3✔
104
  }
105

106
  @Nullable
107
  @Override
108
  public ConfigurationProperty getConfigurationProperty(@Nullable ConfigurationPropertyName name) {
109
    if (name == null) {
2!
110
      return null;
×
111
    }
112
    ConfigurationProperty configurationProperty = super.getConfigurationProperty(name);
4✔
113
    if (configurationProperty != null) {
2✔
114
      return configurationProperty;
2✔
115
    }
116
    for (String candidate : getCache().getMapped(name)) {
13✔
117
      Object value = getPropertySourceProperty(candidate);
4✔
118
      if (value != null) {
2!
119
        Origin origin = PropertySourceOrigin.get(getPropertySource(), candidate);
5✔
120
        return ConfigurationProperty.of(this, name, value, origin);
6✔
121
      }
122
    }
×
123
    return null;
2✔
124
  }
125

126
  @Override
127
  protected Object getSystemEnvironmentProperty(Map<String, Object> systemEnvironment, String name) {
128
    return getCache().getSystemEnvironmentProperty(name);
5✔
129
  }
130

131
  @Override
132
  public Stream<ConfigurationPropertyName> stream() {
133
    ConfigurationPropertyName[] names = getConfigurationPropertyNames();
3✔
134
    return Arrays.stream(names).filter(Objects::nonNull);
5✔
135
  }
136

137
  @Override
138
  public Iterator<ConfigurationPropertyName> iterator() {
139
    return new ConfigurationPropertyNamesIterator(getConfigurationPropertyNames());
6✔
140
  }
141

142
  @Override
143
  public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
144
    ConfigurationPropertyState result = super.containsDescendantOf(name);
4✔
145
    if (result != ConfigurationPropertyState.UNKNOWN) {
3!
146
      return result;
×
147
    }
148
    if (this.ancestorOfCheck == PropertyMapper.DEFAULT_ANCESTOR_OF_CHECK) {
4✔
149
      Set<ConfigurationPropertyName> descendants = getCache().getDescendants();
4✔
150
      if (descendants != null) {
2!
151
        if (name.isEmpty() && !descendants.isEmpty()) {
6✔
152
          return ConfigurationPropertyState.PRESENT;
2✔
153
        }
154
        return !descendants.contains(name) ? ConfigurationPropertyState.ABSENT
7✔
155
                : ConfigurationPropertyState.PRESENT;
1✔
156
      }
157
    }
158
    result = (this.containsDescendantOfCache != null) ? this.containsDescendantOfCache.get(name) : null;
10!
159
    if (result == null) {
2✔
160
      result = (!ancestorOfCheck(name)) ? ConfigurationPropertyState.ABSENT : ConfigurationPropertyState.PRESENT;
8✔
161
      if (this.containsDescendantOfCache != null) {
3!
162
        this.containsDescendantOfCache.put(name, result);
6✔
163
      }
164
    }
165
    return result;
2✔
166
  }
167

168
  private boolean ancestorOfCheck(ConfigurationPropertyName name) {
169
    ConfigurationPropertyName[] candidates = getConfigurationPropertyNames();
3✔
170
    for (ConfigurationPropertyName candidate : candidates) {
16✔
171
      if (candidate != null && this.ancestorOfCheck.test(name, candidate)) {
8!
172
        return true;
2✔
173
      }
174
    }
175
    return false;
2✔
176
  }
177

178
  public ConfigurationPropertyName[] getConfigurationPropertyNames() {
179
    if (!isImmutablePropertySource()) {
3✔
180
      return getCache().getConfigurationPropertyNames(getPropertySource().getPropertyNames());
7✔
181
    }
182
    ConfigurationPropertyName[] configurationPropertyNames = this.configurationPropertyNames;
3✔
183
    if (configurationPropertyNames == null) {
2✔
184
      configurationPropertyNames = getCache()
3✔
185
              .getConfigurationPropertyNames(getPropertySource().getPropertyNames());
4✔
186
      this.configurationPropertyNames = configurationPropertyNames;
3✔
187
    }
188
    return configurationPropertyNames;
2✔
189
  }
190

191
  private Cache getCache() {
192
    return this.cache.get(this::createCache, this::updateCache);
9✔
193
  }
194

195
  private Cache createCache() {
196
    boolean immutable = isImmutablePropertySource();
3✔
197
    boolean captureDescendants = this.ancestorOfCheck == PropertyMapper.DEFAULT_ANCESTOR_OF_CHECK;
8✔
198
    return new Cache(getMappers(), immutable, captureDescendants, isSystemEnvironmentSource());
10✔
199
  }
200

201
  private Cache updateCache(Cache cache) {
202
    cache.update(getPropertySource());
4✔
203
    return cache;
2✔
204
  }
205

206
  public boolean isImmutablePropertySource() {
207
    EnumerablePropertySource<?> source = getPropertySource();
3✔
208
    if (source instanceof OriginLookup<?> originLookup) {
6✔
209
      return originLookup.isImmutable();
3✔
210
    }
211
    if (StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME.equals(source.getName())) {
5✔
212
      return source.getSource() == System.getenv();
8✔
213
    }
214
    return false;
2✔
215
  }
216

217
  @Override
218
  protected EnumerablePropertySource<?> getPropertySource() {
219
    return (EnumerablePropertySource<?>) super.getPropertySource();
4✔
220
  }
221

222
  private static class Cache {
223

224
    private static final ConfigurationPropertyName[] EMPTY_NAMES_ARRAY = {};
4✔
225

226
    private final PropertyMapper[] mappers;
227

228
    private final boolean immutable;
229

230
    private final boolean captureDescendants;
231

232
    private final boolean systemEnvironmentSource;
233

234
    @Nullable
235
    private volatile Data data;
236

237
    Cache(PropertyMapper[] mappers, boolean immutable, boolean captureDescendants, boolean systemEnvironmentSource) {
2✔
238
      this.mappers = mappers;
3✔
239
      this.immutable = immutable;
3✔
240
      this.captureDescendants = captureDescendants;
3✔
241
      this.systemEnvironmentSource = systemEnvironmentSource;
3✔
242
    }
1✔
243

244
    void update(EnumerablePropertySource<?> propertySource) {
245
      if (this.data == null || !this.immutable) {
6!
246
        int count = 0;
2✔
247
        while (true) {
248
          try {
249
            tryUpdate(propertySource);
3✔
250
            return;
1✔
251
          }
252
          catch (ConcurrentModificationException ex) {
×
253
            if (count++ > 10) {
×
254
              throw ex;
×
255
            }
256
          }
×
257
        }
258
      }
259
    }
×
260

261
    private void tryUpdate(EnumerablePropertySource<?> propertySource) {
262
      Data data = this.data;
3✔
263
      String[] lastUpdated = (data != null) ? data.lastUpdated() : null;
7✔
264
      String[] propertyNames = propertySource.getPropertyNames();
3✔
265
      if (lastUpdated != null && Arrays.equals(lastUpdated, propertyNames)) {
6✔
266
        return;
1✔
267
      }
268
      int size = propertyNames.length;
3✔
269
      Map<ConfigurationPropertyName, Set<String>> mappings = cloneOrCreate(
3✔
270
              (data != null) ? data.mappings() : null, size);
7✔
271
      Map<String, ConfigurationPropertyName> reverseMappings = cloneOrCreate(
3✔
272
              (data != null) ? data.reverseMappings() : null, size);
7✔
273
      Set<ConfigurationPropertyName> descendants = (!this.captureDescendants) ? null : new HashSet<>();
9✔
274
      Map<String, Object> systemEnvironmentCopy = (!this.systemEnvironmentSource) ? null : copySource(propertySource);
9✔
275
      for (PropertyMapper propertyMapper : this.mappers) {
17✔
276
        for (String propertyName : propertyNames) {
16✔
277
          if (!reverseMappings.containsKey(propertyName)) {
4✔
278
            ConfigurationPropertyName configurationPropertyName = propertyMapper.map(propertyName);
4✔
279
            if (configurationPropertyName != null && !configurationPropertyName.isEmpty()) {
5!
280
              add(mappings, configurationPropertyName, propertyName);
5✔
281
              reverseMappings.put(propertyName, configurationPropertyName);
5✔
282
            }
283
          }
284
        }
285
      }
286

287
      for (String propertyName : propertyNames) {
16✔
288
        addParents(descendants, reverseMappings.get(propertyName));
7✔
289
      }
290

291
      ConfigurationPropertyName[] configurationPropertyNames = this.immutable
3✔
292
              ? reverseMappings.values().toArray(new ConfigurationPropertyName[0]) : null;
9✔
293
      lastUpdated = this.immutable ? null : propertyNames;
7✔
294
      this.data = new Data(mappings, reverseMappings, descendants, configurationPropertyNames,
11✔
295
              systemEnvironmentCopy, lastUpdated);
296
    }
1✔
297

298
    @SuppressWarnings("unchecked")
299
    private HashMap<String, Object> copySource(EnumerablePropertySource<?> propertySource) {
300
      return new HashMap<>((Map<String, Object>) propertySource.getSource());
7✔
301
    }
302

303
    private <K, V> Map<K, V> cloneOrCreate(@Nullable Map<K, V> source, int size) {
304
      return source != null ? new LinkedHashMap<>(source) : new LinkedHashMap<>(size);
12✔
305
    }
306

307
    private void addParents(@Nullable Set<ConfigurationPropertyName> descendants, @Nullable ConfigurationPropertyName name) {
308
      if (descendants == null || name == null || name.isEmpty()) {
7!
309
        return;
1✔
310
      }
311
      ConfigurationPropertyName parent = name.getParent();
3✔
312
      while (!parent.isEmpty()) {
3✔
313
        if (!descendants.add(parent)) {
4✔
314
          return;
1✔
315
        }
316
        parent = parent.getParent();
4✔
317
      }
318
    }
1✔
319

320
    private <K, T> void add(Map<K, Set<T>> map, K key, T value) {
321
      map.computeIfAbsent(key, (k) -> new HashSet<>()).add(value);
12✔
322
    }
1✔
323

324
    Set<String> getMapped(ConfigurationPropertyName configurationPropertyName) {
325
      return this.data.mappings().getOrDefault(configurationPropertyName, Collections.emptySet());
8✔
326
    }
327

328
    ConfigurationPropertyName[] getConfigurationPropertyNames(String[] propertyNames) {
329
      Data data = this.data;
3✔
330
      ConfigurationPropertyName[] names = data.configurationPropertyNames();
3✔
331
      if (names != null) {
2✔
332
        return names;
2✔
333
      }
334
      Map<String, ConfigurationPropertyName> reverseMappings = data.reverseMappings();
3✔
335
      if (reverseMappings == null || reverseMappings.isEmpty()) {
5!
336
        return EMPTY_NAMES_ARRAY;
2✔
337
      }
338
      names = new ConfigurationPropertyName[propertyNames.length];
4✔
339
      for (int i = 0; i < propertyNames.length; i++) {
8✔
340
        names[i] = reverseMappings.get(propertyNames[i]);
9✔
341
      }
342
      return names;
2✔
343
    }
344

345
    @Nullable
346
    Set<ConfigurationPropertyName> getDescendants() {
347
      return this.data.descendants();
4✔
348
    }
349

350
    Object getSystemEnvironmentProperty(String name) {
351
      return this.data.systemEnvironmentCopy().get(name);
6✔
352
    }
353

354
    private record Data(Map<ConfigurationPropertyName, Set<String>> mappings,
21✔
355
            @Nullable Map<String, ConfigurationPropertyName> reverseMappings,
356
            @Nullable Set<ConfigurationPropertyName> descendants,
357
            @Nullable ConfigurationPropertyName[] configurationPropertyNames,
358
            @Nullable Map<String, Object> systemEnvironmentCopy, @Nullable String[] lastUpdated) {
359

360
    }
361

362
  }
363

364
  /**
365
   * ConfigurationPropertyNames iterator backed by an array.
366
   */
367
  private static class ConfigurationPropertyNamesIterator implements Iterator<ConfigurationPropertyName> {
368

369
    private final ConfigurationPropertyName[] names;
370

371
    private int index = 0;
3✔
372

373
    ConfigurationPropertyNamesIterator(ConfigurationPropertyName[] names) {
2✔
374
      this.names = names;
3✔
375
    }
1✔
376

377
    @Override
378
    public boolean hasNext() {
379
      skipNulls();
2✔
380
      return this.index < this.names.length;
10✔
381
    }
382

383
    @Override
384
    public ConfigurationPropertyName next() {
385
      skipNulls();
2✔
386
      if (this.index >= this.names.length) {
6!
387
        throw new NoSuchElementException();
×
388
      }
389
      return this.names[this.index++];
11✔
390
    }
391

392
    private void skipNulls() {
393
      while (this.index < this.names.length) {
6✔
394
        if (this.names[this.index] != null) {
6✔
395
          return;
1✔
396
        }
397
        this.index++;
7✔
398
      }
399
    }
1✔
400

401
  }
402

403
}
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