• 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

91.82
today-context/src/main/java/infra/context/properties/bind/IndexedElementsBinder.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.lang.annotation.Annotation;
23
import java.util.Collection;
24
import java.util.Collections;
25
import java.util.HashSet;
26
import java.util.Set;
27
import java.util.TreeSet;
28
import java.util.function.Supplier;
29

30
import infra.context.properties.source.ConfigurationProperty;
31
import infra.context.properties.source.ConfigurationPropertyName;
32
import infra.context.properties.source.ConfigurationPropertyName.Form;
33
import infra.context.properties.source.ConfigurationPropertySource;
34
import infra.context.properties.source.IterableConfigurationPropertySource;
35
import infra.core.ResolvableType;
36

37
/**
38
 * Base class for {@link AggregateBinder AggregateBinders} that read a sequential run of
39
 * indexed items.
40
 *
41
 * @param <T> the type being bound
42
 * @author Phillip Webb
43
 * @author Madhura Bhave
44
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
45
 * @since 4.0
46
 */
47
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
48

49
  private static final String[] INDEXES;
50

51
  static {
52
    INDEXES = new String[10];
3✔
53
    for (int i = 0; i < INDEXES.length; i++) {
8✔
54
      INDEXES[i] = "[" + i + "]";
5✔
55
    }
56
  }
1✔
57

58
  IndexedElementsBinder(Binder.Context context) {
59
    super(context);
3✔
60
  }
1✔
61

62
  @Override
63
  protected boolean isAllowRecursiveBinding(@Nullable ConfigurationPropertySource source) {
64
    return source == null || source instanceof IterableConfigurationPropertySource;
9!
65
  }
66

67
  /**
68
   * Bind indexed elements to the supplied collection.
69
   *
70
   * @param name the name of the property to bind
71
   * @param target the target bindable
72
   * @param elementBinder the binder to use for elements
73
   * @param aggregateType the aggregate type, may be a collection or an array
74
   * @param elementType the element type
75
   * @param result the destination for results
76
   */
77
  protected final void bindIndexed(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder,
78
          ResolvableType aggregateType, ResolvableType elementType, IndexedCollectionSupplier result) {
79
    for (ConfigurationPropertySource source : context.getSources()) {
12✔
80
      bindIndexed(source, name, target, elementBinder, result, aggregateType, elementType);
9✔
81
      if (result.wasSupplied() && result.get() != null) {
6!
82
        return;
1✔
83
      }
84
    }
1✔
85
  }
1✔
86

87
  private void bindIndexed(ConfigurationPropertySource source, ConfigurationPropertyName root, Bindable<?> target,
88
          AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType aggregateType,
89
          ResolvableType elementType) {
90
    ConfigurationProperty property = source.getConfigurationProperty(root);
4✔
91
    if (property != null) {
2✔
92
      context.setConfigurationProperty(property);
4✔
93
      bindValue(target, collection.get(), aggregateType, elementType, property.getValue());
11✔
94
    }
95
    else {
96
      bindIndexed(source, root, elementBinder, collection, elementType);
7✔
97
    }
98
  }
1✔
99

100
  private void bindValue(Bindable<?> target, Collection<Object> collection, ResolvableType aggregateType,
101
          ResolvableType elementType, @Nullable Object value) {
102
    if (value == null || (value instanceof CharSequence cs && cs.isEmpty())) {
11!
103
      return;
1✔
104
    }
105
    Object aggregate = convert(value, aggregateType, target.getAnnotations());
7✔
106
    ResolvableType collectionType = ResolvableType.forClassWithGenerics(collection.getClass(), elementType);
10✔
107
    Collection<Object> elements = convert(aggregate, collectionType);
8✔
108
    collection.addAll(elements);
4✔
109
  }
1✔
110

111
  private void bindIndexed(ConfigurationPropertySource source, ConfigurationPropertyName root,
112
          AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType elementType) {
113
    Set<String> knownIndexedChildren = Collections.emptySet();
2✔
114
    if (source instanceof IterableConfigurationPropertySource iterableSource) {
6✔
115
      knownIndexedChildren = getKnownIndexedChildren(iterableSource, root);
5✔
116
    }
117
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
7!
118
      ConfigurationPropertyName name = appendIndex(root, i);
5✔
119
      Object value = elementBinder.bind(name, Bindable.of(elementType), source);
7✔
120
      if (value == null) {
2✔
121
        break;
1✔
122
      }
123
      if (!knownIndexedChildren.isEmpty()) {
3✔
124
        knownIndexedChildren.remove(name.getLastElement(Form.UNIFORM));
6✔
125
      }
126
      collection.get().add(value);
6✔
127
    }
128
    if (source instanceof IterableConfigurationPropertySource iterableSource) {
6✔
129
      assertNoUnboundChildren(knownIndexedChildren, iterableSource, root);
5✔
130
    }
131
  }
1✔
132

133
  private Set<String> getKnownIndexedChildren(IterableConfigurationPropertySource source, ConfigurationPropertyName root) {
134
    var knownIndexedChildren = new HashSet<String>();
4✔
135
    for (ConfigurationPropertyName name : source.filter(root::isAncestorOf)) {
16✔
136
      ConfigurationPropertyName choppedName = name.chop(root.getNumberOfElements() + 1);
7✔
137
      if (choppedName.isLastElementIndexed()) {
3!
138
        knownIndexedChildren.add(choppedName.getLastElement(Form.UNIFORM));
6✔
139
      }
140
    }
1✔
141
    return knownIndexedChildren;
2✔
142
  }
143

144
  private void assertNoUnboundChildren(Set<String> unboundIndexedChildren,
145
          IterableConfigurationPropertySource filteredSource, ConfigurationPropertyName root) {
146
    if (unboundIndexedChildren.isEmpty()) {
3✔
147
      return;
1✔
148
    }
149
    TreeSet<ConfigurationProperty> unboundProperties = new TreeSet<>();
4✔
150
    for (ConfigurationPropertyName name : filteredSource) {
10✔
151
      ConfigurationPropertyName choppedName = name.chop(root.getNumberOfElements() + 1);
7✔
152
      if (choppedName.isLastElementIndexed()
6!
153
              && unboundIndexedChildren.contains(choppedName.getLastElement(Form.UNIFORM))) {
3✔
154
        unboundProperties.add(filteredSource.getConfigurationProperty(name));
6✔
155
      }
156
    }
1✔
157
    if (!unboundProperties.isEmpty()) {
3!
158
      throw new UnboundConfigurationPropertiesException(unboundProperties);
5✔
159
    }
160
  }
×
161

162
  private ConfigurationPropertyName appendIndex(ConfigurationPropertyName root, int i) {
163
    return root.append((i < INDEXES.length) ? INDEXES[i] : "[" + i + "]");
13✔
164
  }
165

166
  @Nullable
167
  private <C> C convert(@Nullable Object value, ResolvableType type, Annotation... annotations) {
168
    value = context.getPlaceholdersResolver().resolvePlaceholders(value);
6✔
169
    return context.getConverter().convert(value, type, annotations);
8✔
170
  }
171

172
  /**
173
   * {@link AggregateBinder.AggregateSupplier AggregateSupplier} for an indexed
174
   * collection.
175
   */
176
  protected static class IndexedCollectionSupplier extends AggregateSupplier<Collection<Object>> {
177

178
    public IndexedCollectionSupplier(Supplier<Collection<Object>> supplier) {
179
      super(supplier);
3✔
180
    }
1✔
181

182
  }
183

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