• 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

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 org.jspecify.annotations.Nullable;
21

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

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

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

63
  private final BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> ancestorOfCheck;
64

65
  private final SoftReferenceConfigurationPropertyCache<Cache> cache;
66

67
  private volatile ConfigurationPropertyName @Nullable [] configurationPropertyNames;
68

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

223
  private static class Cache {
224

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

227
    private final PropertyMapper[] mappers;
228

229
    private final boolean immutable;
230

231
    private final boolean captureDescendants;
232

233
    private final boolean systemEnvironmentSource;
234

235
    @Nullable
236
    private volatile Data data;
237

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

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

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

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

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

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

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

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

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

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

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

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

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

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

361
    }
362

363
  }
364

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

370
    private final ConfigurationPropertyName[] names;
371

372
    private int index = 0;
3✔
373

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

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

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

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

402
  }
403

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