• 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.15
today-context/src/main/java/infra/context/properties/bind/Bindable.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.lang.reflect.Array;
24
import java.util.Arrays;
25
import java.util.EnumSet;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Objects;
29
import java.util.Set;
30
import java.util.function.Supplier;
31

32
import infra.context.properties.source.ConfigurationProperty;
33
import infra.core.ResolvableType;
34
import infra.core.style.ToStringBuilder;
35
import infra.lang.Assert;
36
import infra.lang.Constant;
37
import infra.util.ObjectUtils;
38
import infra.util.function.SingletonSupplier;
39

40
/**
41
 * Source that can be bound by a {@link Binder}.
42
 *
43
 * @param <T> the source type
44
 * @author Phillip Webb
45
 * @author Madhura Bhave
46
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
47
 * @see Bindable#of(Class)
48
 * @see Bindable#of(ResolvableType)
49
 * @since 4.0
50
 */
51
public final class Bindable<T> {
52

53
  private static final EnumSet<BindRestriction> NO_BIND_RESTRICTIONS = EnumSet.noneOf(BindRestriction.class);
4✔
54

55
  private final ResolvableType type;
56

57
  private final ResolvableType boxedType;
58

59
  @Nullable
60
  private final Supplier<T> value;
61

62
  private final Annotation[] annotations;
63

64
  private final EnumSet<BindRestriction> bindRestrictions;
65

66
  @Nullable
67
  private final BindMethod bindMethod;
68

69
  private Bindable(ResolvableType type, ResolvableType boxedType,
70
          @Nullable Supplier<T> value, Annotation[] annotations,
71
          EnumSet<BindRestriction> bindRestrictions, @Nullable BindMethod bindMethod) {
2✔
72
    this.type = type;
3✔
73
    this.boxedType = boxedType;
3✔
74
    this.value = value;
3✔
75
    this.annotations = annotations;
3✔
76
    this.bindRestrictions = bindRestrictions;
3✔
77
    this.bindMethod = bindMethod;
3✔
78
  }
1✔
79

80
  /**
81
   * Return the type of the item to bind.
82
   *
83
   * @return the type being bound
84
   */
85
  public ResolvableType getType() {
86
    return this.type;
3✔
87
  }
88

89
  /**
90
   * Return the boxed type of the item to bind.
91
   *
92
   * @return the boxed type for the item being bound
93
   */
94
  public ResolvableType getBoxedType() {
95
    return this.boxedType;
3✔
96
  }
97

98
  /**
99
   * Return a supplier that provides the object value or {@code null}.
100
   *
101
   * @return the value or {@code null}
102
   */
103
  @Nullable
104
  public Supplier<T> getValue() {
105
    return this.value;
3✔
106
  }
107

108
  /**
109
   * Return any associated annotations that could affect binding.
110
   *
111
   * @return the associated annotations
112
   */
113
  public Annotation[] getAnnotations() {
114
    return this.annotations;
3✔
115
  }
116

117
  /**
118
   * Return a single associated annotations that could affect binding.
119
   *
120
   * @param <A> the annotation type
121
   * @param type annotation type
122
   * @return the associated annotation or {@code null}
123
   */
124
  @SuppressWarnings("unchecked")
125
  @Nullable
126
  public <A extends Annotation> A getAnnotation(Class<A> type) {
127
    for (Annotation annotation : this.annotations) {
17✔
128
      if (type.isInstance(annotation)) {
4✔
129
        return (A) annotation;
2✔
130
      }
131
    }
132
    return null;
2✔
133
  }
134

135
  /**
136
   * Returns {@code true} if the specified bind restriction has been added.
137
   *
138
   * @param bindRestriction the bind restriction to check
139
   * @return if the bind restriction has been added
140
   */
141
  public boolean hasBindRestriction(BindRestriction bindRestriction) {
142
    return this.bindRestrictions.contains(bindRestriction);
5✔
143
  }
144

145
  /**
146
   * Returns the {@link BindMethod method} to be used to bind this bindable, or
147
   * {@code null} if no specific binding method is required.
148
   *
149
   * @return the bind method or {@code null}
150
   */
151
  @Nullable
152
  public BindMethod getBindMethod() {
153
    return this.bindMethod;
3✔
154
  }
155

156
  @Override
157
  public boolean equals(Object obj) {
158
    if (this == obj) {
3✔
159
      return true;
2✔
160
    }
161
    if (obj == null || getClass() != obj.getClass()) {
7!
162
      return false;
×
163
    }
164
    Bindable<?> other = (Bindable<?>) obj;
3✔
165
    return Objects.equals(bindMethod, other.bindMethod)
9!
166
            && Objects.equals(type.resolve(), other.type.resolve())
10!
167
            && ObjectUtils.nullSafeEquals(this.bindRestrictions, other.bindRestrictions)
6!
168
            && ObjectUtils.nullSafeEquals(this.annotations, other.annotations);
4!
169
  }
170

171
  @Override
172
  public int hashCode() {
173
    return Objects.hash(type, Arrays.hashCode(annotations), bindRestrictions, bindMethod);
26✔
174
  }
175

176
  @Override
177
  public String toString() {
178
    ToStringBuilder creator = new ToStringBuilder(this);
5✔
179
    creator.append("type", this.type);
6✔
180
    creator.append("value", value != null ? "provided" : "none");
9!
181
    creator.append("annotations", this.annotations);
6✔
182
    creator.append("bindMethod", this.bindMethod);
6✔
183
    return creator.toString();
3✔
184
  }
185

186
  /**
187
   * Create an updated {@link Bindable} instance with the specified annotations.
188
   *
189
   * @param annotations the annotations
190
   * @return an updated {@link Bindable}
191
   */
192
  public Bindable<T> withAnnotations(Annotation @Nullable ... annotations) {
193
    return new Bindable<>(this.type, this.boxedType, this.value,
9✔
194
            annotations != null ? annotations : Constant.EMPTY_ANNOTATIONS,
9✔
195
            NO_BIND_RESTRICTIONS, this.bindMethod);
196
  }
197

198
  /**
199
   * Create an updated {@link Bindable} instance with an existing value. Implies that
200
   * Java Bean binding will be used.
201
   *
202
   * @param existingValue the existing value
203
   * @return an updated {@link Bindable}
204
   */
205
  @SuppressWarnings("NullAway")
206
  public Bindable<T> withExistingValue(@Nullable T existingValue) {
207
    if (!(existingValue == null || this.type.isArray() || boxedType.resolve().isInstance(existingValue))) {
12!
208
      throw new IllegalArgumentException("ExistingValue must be an instance of " + this.type);
8✔
209
    }
210
    Assert.state(this.bindMethod != BindMethod.VALUE_OBJECT, "An existing value cannot be provided when binding as a value object");
9✔
211
    Supplier<T> value = existingValue != null ? SingletonSupplier.valueOf(existingValue) : null;
6!
212
    return new Bindable<>(this.type, this.boxedType, value,
14✔
213
            this.annotations, this.bindRestrictions, BindMethod.JAVA_BEAN);
214
  }
215

216
  /**
217
   * Create an updated {@link Bindable} instance with a value supplier.
218
   *
219
   * @param suppliedValue the supplier for the value
220
   * @return an updated {@link Bindable}
221
   */
222
  public Bindable<T> withSuppliedValue(@Nullable Supplier<T> suppliedValue) {
223
    return new Bindable<>(this.type, this.boxedType, suppliedValue,
15✔
224
            this.annotations, this.bindRestrictions, this.bindMethod);
225
  }
226

227
  /**
228
   * Create an updated {@link Bindable} instance with additional bind restrictions.
229
   *
230
   * @param additionalRestrictions any additional restrictions to apply
231
   * @return an updated {@link Bindable}
232
   */
233
  public Bindable<T> withBindRestrictions(BindRestriction... additionalRestrictions) {
234
    EnumSet<BindRestriction> bindRestrictions = EnumSet.copyOf(this.bindRestrictions);
4✔
235
    bindRestrictions.addAll(Arrays.asList(additionalRestrictions));
5✔
236
    return new Bindable<>(this.type, this.boxedType, this.value,
15✔
237
            this.annotations, bindRestrictions, this.bindMethod);
238
  }
239

240
  /**
241
   * Create an updated {@link Bindable} instance with a specifc bind method. To use
242
   * {@link BindMethod#VALUE_OBJECT value object binding}, the current instance must not
243
   * have an existing or supplied value.
244
   *
245
   * @param bindMethod the method to use to bind the bindable
246
   * @return an updated {@link Bindable}
247
   */
248
  public Bindable<T> withBindMethod(@Nullable BindMethod bindMethod) {
249
    Assert.state(bindMethod != BindMethod.VALUE_OBJECT || this.value == null,
11✔
250
            "Value object binding cannot be used with an existing or supplied value");
251
    return new Bindable<>(this.type, this.boxedType, this.value,
15✔
252
            this.annotations, this.bindRestrictions, bindMethod);
253
  }
254

255
  /**
256
   * Create a new {@link Bindable} of the type of the specified instance with an
257
   * existing value equal to the instance.
258
   *
259
   * @param <T> the source type
260
   * @param instance the instance (must not be {@code null})
261
   * @return a {@link Bindable} instance
262
   * @see #of(ResolvableType)
263
   * @see #withExistingValue(Object)
264
   */
265
  @SuppressWarnings("unchecked")
266
  public static <T> Bindable<T> ofInstance(T instance) {
267
    Assert.notNull(instance, "Instance is required");
3✔
268
    Class<T> type = (Class<T>) instance.getClass();
3✔
269
    return of(type).withExistingValue(instance);
5✔
270
  }
271

272
  /**
273
   * Create a new {@link Bindable} of the specified type.
274
   *
275
   * @param <T> the source type
276
   * @param type the type (must not be {@code null})
277
   * @return a {@link Bindable} instance
278
   * @see #of(ResolvableType)
279
   */
280
  public static <T> Bindable<T> of(Class<T> type) {
281
    Assert.notNull(type, "Type is required");
3✔
282
    return of(ResolvableType.forClass(type));
4✔
283
  }
284

285
  /**
286
   * Create a new {@link Bindable} {@link List} of the specified element type.
287
   *
288
   * @param <E> the element type
289
   * @param elementType the list element type
290
   * @return a {@link Bindable} instance
291
   */
292
  public static <E> Bindable<List<E>> listOf(Class<E> elementType) {
293
    return of(ResolvableType.forClassWithGenerics(List.class, elementType));
10✔
294
  }
295

296
  /**
297
   * Create a new {@link Bindable} {@link Set} of the specified element type.
298
   *
299
   * @param <E> the element type
300
   * @param elementType the set element type
301
   * @return a {@link Bindable} instance
302
   */
303
  public static <E> Bindable<Set<E>> setOf(Class<E> elementType) {
304
    return of(ResolvableType.forClassWithGenerics(Set.class, elementType));
10✔
305
  }
306

307
  /**
308
   * Create a new {@link Bindable} {@link Map} of the specified key and value type.
309
   *
310
   * @param <K> the key type
311
   * @param <V> the value type
312
   * @param keyType the map key type
313
   * @param valueType the map value type
314
   * @return a {@link Bindable} instance
315
   */
316
  public static <K, V> Bindable<Map<K, V>> mapOf(Class<K> keyType, Class<V> valueType) {
317
    return of(ResolvableType.forClassWithGenerics(Map.class, keyType, valueType));
14✔
318
  }
319

320
  /**
321
   * Create a new {@link Bindable} of the specified type.
322
   *
323
   * @param <T> the source type
324
   * @param type the type (must not be {@code null})
325
   * @return a {@link Bindable} instance
326
   * @see #of(Class)
327
   */
328
  public static <T> Bindable<T> of(ResolvableType type) {
329
    Assert.notNull(type, "Type is required");
3✔
330
    ResolvableType boxedType = box(type);
3✔
331
    return new Bindable<>(type, boxedType, null,
10✔
332
            Constant.EMPTY_ANNOTATIONS, NO_BIND_RESTRICTIONS, null);
333
  }
334

335
  private static ResolvableType box(ResolvableType type) {
336
    Class<?> resolved = type.resolve();
3✔
337
    if (resolved != null && resolved.isPrimitive()) {
5✔
338
      Object array = Array.newInstance(resolved, 1);
4✔
339
      Class<?> wrapperType = Array.get(array, 0).getClass();
5✔
340
      return ResolvableType.forClass(wrapperType);
3✔
341
    }
342
    if (resolved != null && resolved.isArray()) {
5✔
343
      return ResolvableType.forArrayComponent(box(type.getComponentType()));
5✔
344
    }
345
    return type;
2✔
346
  }
347

348
  /**
349
   * Restrictions that can be applied when binding values.
350
   */
351
  public enum BindRestriction {
3✔
352

353
    /**
354
     * Do not bind direct {@link ConfigurationProperty} matches.
355
     */
356
    NO_DIRECT_PROPERTY
6✔
357

358
  }
359

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