• 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

95.18
today-context/src/main/java/infra/context/properties/bind/handler/NoUnboundElementsBindHandler.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.handler;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.HashSet;
23
import java.util.Set;
24
import java.util.TreeSet;
25
import java.util.function.Function;
26

27
import infra.context.properties.bind.AbstractBindHandler;
28
import infra.context.properties.bind.BindContext;
29
import infra.context.properties.bind.BindHandler;
30
import infra.context.properties.bind.Bindable;
31
import infra.context.properties.bind.UnboundConfigurationPropertiesException;
32
import infra.context.properties.source.ConfigurationProperty;
33
import infra.context.properties.source.ConfigurationPropertyName;
34
import infra.context.properties.source.ConfigurationPropertySource;
35
import infra.context.properties.source.IterableConfigurationPropertySource;
36

37
/**
38
 * {@link BindHandler} to enforce that all configuration properties under the root name
39
 * have been bound.
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
public class NoUnboundElementsBindHandler extends AbstractBindHandler {
47

48
  private final Set<ConfigurationPropertyName> boundNames = new HashSet<>();
5✔
49

50
  private final Set<ConfigurationPropertyName> attemptedNames = new HashSet<>();
5✔
51

52
  private final Function<ConfigurationPropertySource, Boolean> filter;
53

54
  NoUnboundElementsBindHandler() {
55
    this(DEFAULT, (configurationPropertySource) -> true);
7✔
56
  }
1✔
57

58
  public NoUnboundElementsBindHandler(BindHandler parent, Function<ConfigurationPropertySource, Boolean> filter) {
59
    super(parent);
3✔
60
    this.filter = filter;
3✔
61
  }
1✔
62

63
  @Nullable
64
  @Override
65
  public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
66
    this.attemptedNames.add(name);
5✔
67
    return super.onStart(name, target, context);
6✔
68
  }
69

70
  @Nullable
71
  @Override
72
  public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
73
    this.boundNames.add(name);
5✔
74
    return super.onSuccess(name, target, context, result);
7✔
75
  }
76

77
  @Nullable
78
  @Override
79
  public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error)
80
          throws Exception {
81
    if (error instanceof UnboundConfigurationPropertiesException) {
3!
82
      throw error;
2✔
83
    }
84
    return super.onFailure(name, target, context, error);
×
85
  }
86

87
  @Override
88
  public void onFinish(ConfigurationPropertyName name, Bindable<?> target, BindContext context, @Nullable Object result) {
89
    if (context.getDepth() == 0) {
3✔
90
      checkNoUnboundElements(name, context);
4✔
91
    }
92
  }
1✔
93

94
  private void checkNoUnboundElements(ConfigurationPropertyName name, BindContext context) {
95
    TreeSet<ConfigurationProperty> unbound = new TreeSet<>();
4✔
96
    for (ConfigurationPropertySource source : context.getSources()) {
11✔
97
      if (source instanceof IterableConfigurationPropertySource iterableSource && filter.apply(source)) {
13✔
98
        collectUnbound(name, unbound, iterableSource);
5✔
99
      }
100
    }
1✔
101
    if (!unbound.isEmpty()) {
3✔
102
      throw new UnboundConfigurationPropertiesException(unbound);
5✔
103
    }
104
  }
1✔
105

106
  private void collectUnbound(ConfigurationPropertyName name,
107
          Set<ConfigurationProperty> unbound, IterableConfigurationPropertySource source) {
108
    IterableConfigurationPropertySource filtered = source.filter((candidate) -> isUnbound(name, candidate));
11✔
109
    for (ConfigurationPropertyName unboundName : filtered) {
10✔
110
      try {
111
        unbound.add(source.filter((candidate) -> isUnbound(name, candidate)).getConfigurationProperty(unboundName));
15✔
112
      }
113
      catch (Exception ignored) {
×
114
      }
1✔
115
    }
1✔
116
  }
1✔
117

118
  private boolean isUnbound(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
119
    if (name.isAncestorOf(candidate)) {
4✔
120
      return !this.boundNames.contains(candidate) && !isOverriddenCollectionElement(candidate);
13✔
121
    }
122
    return false;
2✔
123
  }
124

125
  private boolean isOverriddenCollectionElement(ConfigurationPropertyName candidate) {
126
    int lastIndex = candidate.getNumberOfElements() - 1;
5✔
127
    if (candidate.isLastElementIndexed()) {
3✔
128
      ConfigurationPropertyName propertyName = candidate.chop(lastIndex);
4✔
129
      return this.boundNames.contains(propertyName);
5✔
130
    }
131
    Indexed indexed = getIndexed(candidate);
4✔
132
    if (indexed != null) {
2✔
133
      String zeroethProperty = indexed.name + "[0]";
4✔
134
      if (this.boundNames.contains(ConfigurationPropertyName.of(zeroethProperty))) {
6!
135
        String nestedZeroethProperty = zeroethProperty + "." + indexed.nestedPropertyName;
5✔
136
        return isCandidateValidPropertyName(nestedZeroethProperty);
4✔
137
      }
138
    }
139
    return false;
2✔
140
  }
141

142
  private boolean isCandidateValidPropertyName(String nestedZeroethProperty) {
143
    return this.attemptedNames.contains(ConfigurationPropertyName.of(nestedZeroethProperty));
6✔
144
  }
145

146
  @Nullable
147
  private Indexed getIndexed(ConfigurationPropertyName candidate) {
148
    for (int i = 0; i < candidate.getNumberOfElements(); i++) {
8✔
149
      if (candidate.isNumericIndex(i)) {
4✔
150
        return new Indexed(candidate.chop(i).toString(),
12✔
151
                candidate.getElement(i + 1, ConfigurationPropertyName.Form.UNIFORM));
2✔
152
      }
153
    }
154
    return null;
2✔
155
  }
156

157
  private record Indexed(String name, String nestedPropertyName) {
9✔
158

159
  }
160

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