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

TAKETODAY / today-infrastructure / 20632861616

01 Jan 2026 04:53AM UTC coverage: 84.18% (-0.3%) from 84.439%
20632861616

push

github

TAKETODAY
:sparkles: ApplicationType 支持通过 SPI 获取

55643 of 70608 branches covered (78.81%)

Branch coverage included in aggregate %.

130472 of 150485 relevant lines covered (86.7%)

3.73 hits per line

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

85.34
today-context/src/main/java/infra/validation/DataBinder.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.validation;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.beans.PropertyEditor;
23
import java.lang.annotation.Annotation;
24
import java.lang.reflect.Array;
25
import java.lang.reflect.Constructor;
26
import java.lang.reflect.Field;
27
import java.util.ArrayList;
28
import java.util.Arrays;
29
import java.util.Collection;
30
import java.util.Collections;
31
import java.util.HashMap;
32
import java.util.HashSet;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.Optional;
36
import java.util.Set;
37
import java.util.SortedSet;
38
import java.util.TreeSet;
39
import java.util.function.Predicate;
40

41
import infra.beans.BeanInstantiationException;
42
import infra.beans.BeanUtils;
43
import infra.beans.BeanWrapper;
44
import infra.beans.ConfigurablePropertyAccessor;
45
import infra.beans.PropertyAccessException;
46
import infra.beans.PropertyAccessorUtils;
47
import infra.beans.PropertyBatchUpdateException;
48
import infra.beans.PropertyEditorRegistrar;
49
import infra.beans.PropertyEditorRegistry;
50
import infra.beans.PropertyValue;
51
import infra.beans.PropertyValues;
52
import infra.beans.SimpleTypeConverter;
53
import infra.beans.TypeConverter;
54
import infra.beans.TypeMismatchException;
55
import infra.core.DefaultParameterNameDiscoverer;
56
import infra.core.MethodParameter;
57
import infra.core.ResolvableType;
58
import infra.core.TypeDescriptor;
59
import infra.core.conversion.ConversionService;
60
import infra.format.Formatter;
61
import infra.format.support.FormatterPropertyEditorAdapter;
62
import infra.lang.Assert;
63
import infra.logging.Logger;
64
import infra.logging.LoggerFactory;
65
import infra.util.CollectionUtils;
66
import infra.util.ObjectUtils;
67
import infra.util.PatternMatchUtils;
68
import infra.util.StringUtils;
69
import infra.validation.annotation.ValidationAnnotationUtils;
70

71
/**
72
 * Binder that allows for setting property values on a target object, including
73
 * support for validation and binding result analysis.
74
 *
75
 * <p>The binding process can be customized by specifying allowed field patterns,
76
 * required fields, custom editors, etc.
77
 *
78
 * <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
79
 * parts of the object graph that are not meant to be accessed or modified by
80
 * external clients. Therefore, the design and use of data binding should be considered
81
 * carefully with regard to security. For more details, please refer to the dedicated
82
 * sections on data binding for
83
 * <a href="https://docs.today-tech.cn/today-infrastructure/web/webmvc.html#mvc">Infra Web MVC</a>
84
 * in the reference manual.
85
 *
86
 * <p>The binding results can be examined via the {@link BindingResult} interface,
87
 * extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
88
 * Missing fields and property access exceptions will be converted to {@link FieldError FieldErrors},
89
 * collected in the Errors instance, using the following error codes:
90
 *
91
 * <ul>
92
 * <li>Missing field error: "required"
93
 * <li>Type mismatch error: "typeMismatch"
94
 * <li>Method invocation error: "methodInvocation"
95
 * </ul>
96
 *
97
 * <p>By default, binding errors get resolved through the {@link BindingErrorProcessor}
98
 * strategy, processing for missing fields and property access exceptions: see the
99
 * {@link #setBindingErrorProcessor} method. You can override the default strategy
100
 * if needed, for example to generate different error codes.
101
 *
102
 * <p>Custom validation errors can be added afterwards. You will typically want to resolve
103
 * such error codes into proper user-visible error messages; this can be achieved through
104
 * resolving each error via a {@link infra.context.MessageSource}, which is
105
 * able to resolve an {@link ObjectError}/{@link FieldError} through its
106
 * {@link infra.context.MessageSource#getMessage(infra.context.MessageSourceResolvable, java.util.Locale)}
107
 * method. The list of message codes can be customized through the {@link MessageCodesResolver}
108
 * strategy: see the {@link #setMessageCodesResolver} method. {@link DefaultMessageCodesResolver}'s
109
 * javadoc states details on the default resolution rules.
110
 *
111
 * <p>This generic data binder can be used in any kind of environment.
112
 *
113
 * @author Rod Johnson
114
 * @author Juergen Hoeller
115
 * @author Rob Harrop
116
 * @author Stephane Nicoll
117
 * @author Kazuki Shimizu
118
 * @author Sam Brannen
119
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
120
 * @see #setAllowedFields
121
 * @see #setRequiredFields
122
 * @see #registerCustomEditor
123
 * @see #setMessageCodesResolver
124
 * @see #setBindingErrorProcessor
125
 * @see #bind
126
 * @see #getBindingResult
127
 * @see DefaultMessageCodesResolver
128
 * @see DefaultBindingErrorProcessor
129
 * @see infra.context.MessageSource
130
 * @since 4.0
131
 */
132
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
133

134
  /** Default object name used for binding: "target". */
135
  public static final String DEFAULT_OBJECT_NAME = "target";
136

137
  /** Default limit for array and collection growing: 256. */
138
  public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
139

140
  /**
141
   * We'll create a lot of DataBinder instances: Let's use a static logger.
142
   */
143
  protected static final Logger logger = LoggerFactory.getLogger(DataBinder.class);
4✔
144

145
  /** Internal constant for constructor binding via "[]". */
146
  private static final int NO_INDEX = -1;
147

148
  @Nullable
149
  private Object target;
150

151
  @Nullable
152
  ResolvableType targetType;
153

154
  private final String objectName;
155

156
  @Nullable
157
  private AbstractPropertyBindingResult bindingResult;
158

159
  private boolean directFieldAccess = false;
3✔
160

161
  @Nullable
162
  private ExtendedTypeConverter typeConverter;
163

164
  private boolean declarativeBinding = false;
3✔
165

166
  private boolean ignoreUnknownFields = true;
3✔
167

168
  private boolean ignoreInvalidFields = false;
3✔
169

170
  private boolean autoGrowNestedPaths = true;
3✔
171

172
  private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
3✔
173

174
  private String @Nullable [] allowedFields;
175

176
  private String @Nullable [] disallowedFields;
177

178
  private String @Nullable [] requiredFields;
179

180
  @Nullable
181
  private NameResolver nameResolver;
182

183
  @Nullable
184
  private ConversionService conversionService;
185

186
  @Nullable
187
  private MessageCodesResolver messageCodesResolver;
188

189
  private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
5✔
190

191
  private final ArrayList<Validator> validators = new ArrayList<>();
5✔
192

193
  @Nullable
194
  private Predicate<Validator> excludedValidators;
195

196
  /**
197
   * Create a new DataBinder instance, with default object name.
198
   *
199
   * @param target the target object to bind onto (or {@code null}
200
   * if the binder is just used to convert a plain parameter value)
201
   * @see #DEFAULT_OBJECT_NAME
202
   */
203
  public DataBinder(@Nullable Object target) {
204
    this(target, DEFAULT_OBJECT_NAME);
4✔
205
  }
1✔
206

207
  /**
208
   * Create a new DataBinder instance.
209
   *
210
   * @param target the target object to bind onto (or {@code null}
211
   * if the binder is just used to convert a plain parameter value)
212
   * @param objectName the name of the target object
213
   */
214
  public DataBinder(@Nullable Object target, String objectName) {
2✔
215
    this.target = ObjectUtils.unwrapOptional(target);
4✔
216
    this.objectName = objectName;
3✔
217
  }
1✔
218

219
  /**
220
   * Return the wrapped target object.
221
   * <p>If the target object is {@code null} and {@link #getTargetType()} is set,
222
   * then {@link #construct(ValueResolver)} may be called to create the target.
223
   */
224
  @Nullable
225
  public Object getTarget() {
226
    return this.target;
3✔
227
  }
228

229
  /**
230
   * Return the name of the bound object.
231
   */
232
  public String getObjectName() {
233
    return this.objectName;
3✔
234
  }
235

236
  /**
237
   * Set the type for the target object. When the target is {@code null},
238
   * setting the targetType allows using {@link #construct} to create the target.
239
   *
240
   * @param targetType the type of the target object
241
   * @see #construct
242
   */
243
  public void setTargetType(ResolvableType targetType) {
244
    Assert.state(this.target == null, "targetType is used to for target creation but target is already set");
7!
245
    this.targetType = targetType;
3✔
246
  }
1✔
247

248
  /**
249
   * Return the {@link #setTargetType configured} type for the target object.
250
   */
251
  @Nullable
252
  public ResolvableType getTargetType() {
253
    return this.targetType;
3✔
254
  }
255

256
  /**
257
   * Set whether this binder should attempt to "auto-grow" a nested path that contains a null value.
258
   * <p>If "true", a null path location will be populated with a default object value and traversed
259
   * instead of resulting in an exception. This flag also enables auto-growth of collection elements
260
   * when accessing an out-of-bounds index.
261
   * <p>Default is "true" on a standard DataBinder. Note that since 4.0 this feature is supported
262
   * for bean property access (DataBinder's default mode) and field access.
263
   * <p>Used for setter/field injection via {@link #bind(PropertyValues)}, and not
264
   * applicable to constructor initialization via {@link #construct(ValueResolver)}.
265
   *
266
   * @see #initBeanPropertyAccess()
267
   * @see BeanWrapper#setAutoGrowNestedPaths
268
   */
269
  public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
270
    Assert.state(this.bindingResult == null,
7!
271
            "DataBinder is already initialized - call setAutoGrowNestedPaths before other configuration methods");
272
    this.autoGrowNestedPaths = autoGrowNestedPaths;
3✔
273
  }
1✔
274

275
  /**
276
   * Return whether "auto-growing" of nested paths has been activated.
277
   */
278
  public boolean isAutoGrowNestedPaths() {
279
    return this.autoGrowNestedPaths;
3✔
280
  }
281

282
  /**
283
   * Specify the limit for array and collection auto-growing.
284
   * <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
285
   * Raise this limit if your auto-growing needs are unusually high.
286
   * <p>Used for setter/field injection via {@link #bind(PropertyValues)}, and not
287
   * applicable to constructor initialization via {@link #construct(ValueResolver)}.
288
   *
289
   * @see #initBeanPropertyAccess()
290
   * @see BeanWrapper#setAutoGrowCollectionLimit
291
   */
292
  public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
293
    Assert.state(this.bindingResult == null,
8✔
294
            "DataBinder is already initialized - call setAutoGrowCollectionLimit before other configuration methods");
295
    this.autoGrowCollectionLimit = autoGrowCollectionLimit;
3✔
296
  }
1✔
297

298
  /**
299
   * Return the current limit for array and collection auto-growing.
300
   */
301
  public int getAutoGrowCollectionLimit() {
302
    return this.autoGrowCollectionLimit;
3✔
303
  }
304

305
  /**
306
   * Initialize standard JavaBean property access for this DataBinder.
307
   * <p>This is the default; an explicit call just leads to eager initialization.
308
   *
309
   * @see #initDirectFieldAccess()
310
   * @see #createBeanPropertyBindingResult()
311
   */
312
  public void initBeanPropertyAccess() {
313
    Assert.state(this.bindingResult == null,
7!
314
            "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
315
    this.directFieldAccess = false;
3✔
316
  }
1✔
317

318
  /**
319
   * Create the {@link AbstractPropertyBindingResult} instance using standard
320
   * JavaBean property access.
321
   */
322
  protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
323
    BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),
5✔
324
            getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
7✔
325

326
    if (this.conversionService != null) {
3✔
327
      result.initConversion(this.conversionService);
4✔
328
    }
329
    if (this.messageCodesResolver != null) {
3✔
330
      result.setMessageCodesResolver(this.messageCodesResolver);
4✔
331
    }
332

333
    return result;
2✔
334
  }
335

336
  /**
337
   * Initialize direct field access for this DataBinder,
338
   * as alternative to the default bean property access.
339
   *
340
   * @see #initBeanPropertyAccess()
341
   * @see #createDirectFieldBindingResult()
342
   */
343
  public void initDirectFieldAccess() {
344
    Assert.state(this.bindingResult == null,
7!
345
            "DataBinder is already initialized - call initDirectFieldAccess before other configuration methods");
346
    this.directFieldAccess = true;
3✔
347
  }
1✔
348

349
  /**
350
   * Create the {@link AbstractPropertyBindingResult} instance using direct
351
   * field access.
352
   */
353
  protected AbstractPropertyBindingResult createDirectFieldBindingResult() {
354
    DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(),
5✔
355
            getObjectName(), isAutoGrowNestedPaths());
5✔
356

357
    if (this.conversionService != null) {
3✔
358
      result.initConversion(this.conversionService);
4✔
359
    }
360
    if (this.messageCodesResolver != null) {
3✔
361
      result.setMessageCodesResolver(this.messageCodesResolver);
4✔
362
    }
363

364
    return result;
2✔
365
  }
366

367
  /**
368
   * Return the internal BindingResult held by this DataBinder,
369
   * as an AbstractPropertyBindingResult.
370
   */
371
  protected AbstractPropertyBindingResult getInternalBindingResult() {
372
    AbstractPropertyBindingResult bindingResult = this.bindingResult;
3✔
373
    if (bindingResult == null) {
2✔
374
      bindingResult = directFieldAccess ? createDirectFieldBindingResult() : createBeanPropertyBindingResult();
9✔
375
      this.bindingResult = bindingResult;
3✔
376
    }
377
    return bindingResult;
2✔
378
  }
379

380
  /**
381
   * Return the underlying PropertyAccessor of this binder's BindingResult.
382
   */
383
  protected ConfigurablePropertyAccessor getPropertyAccessor() {
384
    return getInternalBindingResult().getPropertyAccessor();
4✔
385
  }
386

387
  /**
388
   * Return this binder's underlying SimpleTypeConverter.
389
   */
390
  protected SimpleTypeConverter getSimpleTypeConverter() {
391
    if (this.typeConverter == null) {
3✔
392
      this.typeConverter = new ExtendedTypeConverter();
5✔
393
      if (this.conversionService != null) {
3✔
394
        this.typeConverter.setConversionService(this.conversionService);
5✔
395
      }
396
    }
397
    return this.typeConverter;
3✔
398
  }
399

400
  /**
401
   * Return the underlying TypeConverter of this binder's BindingResult.
402
   */
403
  protected PropertyEditorRegistry getPropertyEditorRegistry() {
404
    if (getTarget() != null) {
3✔
405
      return getInternalBindingResult().getPropertyAccessor();
4✔
406
    }
407
    else {
408
      return getSimpleTypeConverter();
3✔
409
    }
410
  }
411

412
  /**
413
   * Return the underlying TypeConverter of this binder's BindingResult.
414
   */
415
  protected TypeConverter getTypeConverter() {
416
    if (getTarget() != null) {
3!
417
      return getInternalBindingResult().getPropertyAccessor();
×
418
    }
419
    else {
420
      return getSimpleTypeConverter();
3✔
421
    }
422
  }
423

424
  /**
425
   * Return the BindingResult instance created by this DataBinder.
426
   * This allows for convenient access to the binding results after
427
   * a bind operation.
428
   *
429
   * @return the BindingResult instance, to be treated as BindingResult
430
   * or as Errors instance (Errors is a super-interface of BindingResult)
431
   * @see Errors
432
   * @see #bind
433
   */
434
  public BindingResult getBindingResult() {
435
    return getInternalBindingResult();
3✔
436
  }
437

438
  /**
439
   * Set whether to bind only fields explicitly intended for binding including:
440
   * <ul>
441
   * <li>Constructor binding via {@link #construct}.
442
   * <li>Property binding with configured
443
   * {@link #setAllowedFields(String...) allowedFields}.
444
   * </ul>
445
   * <p>Default is "false". Turn this on to limit binding to constructor
446
   * parameters and allowed fields.
447
   */
448
  public void setDeclarativeBinding(boolean declarativeBinding) {
449
    this.declarativeBinding = declarativeBinding;
3✔
450
  }
1✔
451

452
  /**
453
   * Return whether to bind only fields intended for binding.
454
   */
455
  public boolean isDeclarativeBinding() {
456
    return this.declarativeBinding;
3✔
457
  }
458

459
  /**
460
   * Set whether to ignore unknown fields, that is, whether to ignore bind
461
   * parameters that do not have corresponding fields in the target object.
462
   * <p>Default is "true". Turn this off to enforce that all bind parameters
463
   * must have a matching field in the target object.
464
   * <p>Note that this setting only applies to <i>binding</i> operations
465
   * on this DataBinder, not to <i>retrieving</i> values via its
466
   * {@link #getBindingResult() BindingResult}.
467
   * <p>Used for setter/field inject via {@link #bind(PropertyValues)}, and not
468
   * applicable to constructor initialization via {@link #construct(ValueResolver)},
469
   * which uses only the values it needs.
470
   *
471
   * @see #bind
472
   */
473
  public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
474
    this.ignoreUnknownFields = ignoreUnknownFields;
3✔
475
  }
1✔
476

477
  /**
478
   * Return whether to ignore unknown fields when binding.
479
   */
480
  public boolean isIgnoreUnknownFields() {
481
    return this.ignoreUnknownFields;
3✔
482
  }
483

484
  /**
485
   * Set whether to ignore invalid fields, that is, whether to ignore bind
486
   * parameters that have corresponding fields in the target object which are
487
   * not accessible (for example because of null values in the nested path).
488
   * <p>Default is "false". Turn this on to ignore bind parameters for
489
   * nested objects in non-existing parts of the target object graph.
490
   * <p>Note that this setting only applies to <i>binding</i> operations
491
   * on this DataBinder, not to <i>retrieving</i> values via its
492
   * {@link #getBindingResult() BindingResult}.
493
   * <p>Used for setter/field inject via {@link #bind(PropertyValues)}, and not
494
   * applicable to constructor initialization via {@link #construct(ValueResolver)},
495
   * which uses only the values it needs.
496
   *
497
   * @see #bind
498
   */
499
  public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
500
    this.ignoreInvalidFields = ignoreInvalidFields;
3✔
501
  }
1✔
502

503
  /**
504
   * Return whether to ignore invalid fields when binding.
505
   */
506
  public boolean isIgnoreInvalidFields() {
507
    return this.ignoreInvalidFields;
3✔
508
  }
509

510
  /**
511
   * Register field patterns that should be allowed for binding.
512
   * <p>Default is all fields.
513
   * <p>Restrict this for example to avoid unwanted modifications by malicious
514
   * users when binding HTTP request parameters.
515
   * <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
516
   * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
517
   * well as direct equality.
518
   * <p>The default implementation of this method stores allowed field patterns
519
   * in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
520
   * form. Subclasses which override this method must therefore take this into
521
   * account.
522
   * <p>More sophisticated matching can be implemented by overriding the
523
   * {@link #isAllowed} method.
524
   * <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
525
   * <p>Used for setter/field inject via {@link #bind(PropertyValues)}, and not
526
   * applicable to constructor initialization via {@link #construct(ValueResolver)},
527
   * which uses only the values it needs.
528
   *
529
   * @param allowedFields array of allowed field patterns
530
   * @see #setDisallowedFields
531
   * @see #isAllowed(String)
532
   */
533
  public void setAllowedFields(String @Nullable ... allowedFields) {
534
    this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);
4✔
535
  }
1✔
536

537
  /**
538
   * Return the field patterns that should be allowed for binding.
539
   *
540
   * @return array of allowed field patterns
541
   * @see #setAllowedFields(String...)
542
   */
543
  public String @Nullable [] getAllowedFields() {
544
    return this.allowedFields;
3✔
545
  }
546

547
  /**
548
   * Register field patterns that should <i>not</i> be allowed for binding.
549
   * <p>Default is none.
550
   * <p>Mark fields as disallowed, for example to avoid unwanted
551
   * modifications by malicious users when binding HTTP request parameters.
552
   * <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
553
   * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts),
554
   * as well as direct equality.
555
   * <p>The default implementation of this method stores disallowed field
556
   * patterns in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String)
557
   * canonical} form, and subsequently pattern matching in {@link #isAllowed}
558
   * is case-insensitive. Subclasses that override this method must therefore
559
   * take this transformation into account.
560
   * <p>More sophisticated matching can be implemented by overriding the
561
   * {@link #isAllowed} method.
562
   * <p>Alternatively, specify a list of <i>allowed</i> field patterns.
563
   * <p>Used for binding to fields with {@link #bind(PropertyValues)}, and not
564
   * applicable to constructor binding via {@link #construct},
565
   * which uses only the values it needs.
566
   *
567
   * @param disallowedFields array of disallowed field patterns
568
   * @see #setAllowedFields
569
   * @see #isAllowed(String)
570
   */
571
  public void setDisallowedFields(String @Nullable ... disallowedFields) {
572
    if (disallowedFields == null) {
2!
573
      this.disallowedFields = null;
×
574
    }
575
    else {
576
      String[] fieldPatterns = new String[disallowedFields.length];
4✔
577
      for (int i = 0; i < fieldPatterns.length; i++) {
8✔
578
        fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
7✔
579
      }
580
      this.disallowedFields = fieldPatterns;
3✔
581
    }
582
  }
1✔
583

584
  /**
585
   * Return the field patterns that should <i>not</i> be allowed for binding.
586
   *
587
   * @return array of disallowed field patterns
588
   * @see #setDisallowedFields(String...)
589
   */
590
  public String @Nullable [] getDisallowedFields() {
591
    return this.disallowedFields;
3✔
592
  }
593

594
  /**
595
   * Register fields that are required for each binding process.
596
   * <p>If one of the specified fields is not contained in the list of
597
   * incoming property values, a corresponding "missing field" error
598
   * will be created, with error code "required" (by the default
599
   * binding error processor).
600
   * <p>Used for setter/field inject via {@link #bind(PropertyValues)}, and not
601
   * applicable to constructor initialization via {@link #construct(ValueResolver)},
602
   * which uses only the values it needs.
603
   *
604
   * @param requiredFields array of field names
605
   * @see #setBindingErrorProcessor
606
   * @see DefaultBindingErrorProcessor#MISSING_FIELD_ERROR_CODE
607
   */
608
  public void setRequiredFields(String @Nullable ... requiredFields) {
609
    this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);
4✔
610
    if (logger.isDebugEnabled()) {
3!
611
      logger.debug("DataBinder requires binding of required fields [{}]",
×
612
              StringUtils.arrayToCommaDelimitedString(requiredFields));
×
613
    }
614
  }
1✔
615

616
  /**
617
   * Return the fields that are required for each binding process.
618
   *
619
   * @return array of field names
620
   */
621
  public String @Nullable [] getRequiredFields() {
622
    return this.requiredFields;
3✔
623
  }
624

625
  /**
626
   * Configure a resolver to determine the name of the value to bind to a
627
   * constructor parameter in {@link #construct}.
628
   * <p>If not configured, or if the name cannot be resolved, by default
629
   * {@link DefaultParameterNameDiscoverer} is used.
630
   *
631
   * @param nameResolver the resolver to use
632
   */
633
  public void setNameResolver(@Nullable NameResolver nameResolver) {
634
    this.nameResolver = nameResolver;
3✔
635
  }
1✔
636

637
  /**
638
   * Return the {@link #setNameResolver configured} name resolver for
639
   * constructor parameters.
640
   */
641
  @Nullable
642
  public NameResolver getNameResolver() {
643
    return this.nameResolver;
×
644
  }
645

646
  /**
647
   * Set the strategy to use for resolving errors into message codes.
648
   * Applies the given strategy to the underlying errors holder.
649
   * <p>Default is a DefaultMessageCodesResolver.
650
   *
651
   * @see BeanPropertyBindingResult#setMessageCodesResolver
652
   * @see DefaultMessageCodesResolver
653
   */
654
  public void setMessageCodesResolver(@Nullable MessageCodesResolver messageCodesResolver) {
655
    Assert.state(this.messageCodesResolver == null, "DataBinder is already initialized with MessageCodesResolver");
8✔
656
    this.messageCodesResolver = messageCodesResolver;
3✔
657
    if (this.bindingResult != null && messageCodesResolver != null) {
3!
658
      this.bindingResult.setMessageCodesResolver(messageCodesResolver);
×
659
    }
660
  }
1✔
661

662
  /**
663
   * Set the strategy to use for processing binding errors, that is,
664
   * required field errors and {@code PropertyAccessException}s.
665
   * <p>Default is a DefaultBindingErrorProcessor.
666
   *
667
   * @see DefaultBindingErrorProcessor
668
   */
669
  public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
670
    Assert.notNull(bindingErrorProcessor, "BindingErrorProcessor is required");
×
671
    this.bindingErrorProcessor = bindingErrorProcessor;
×
672
  }
×
673

674
  /**
675
   * Return the strategy for processing binding errors.
676
   */
677
  public BindingErrorProcessor getBindingErrorProcessor() {
678
    return this.bindingErrorProcessor;
3✔
679
  }
680

681
  /**
682
   * Set the Validator to apply after each binding step.
683
   *
684
   * @see #addValidators(Validator...)
685
   * @see #replaceValidators(Validator...)
686
   */
687
  public void setValidator(@Nullable Validator validator) {
688
    assertValidators(validator);
8✔
689
    this.validators.clear();
3✔
690
    if (validator != null) {
2!
691
      this.validators.add(validator);
5✔
692
    }
693
  }
1✔
694

695
  private void assertValidators(@Nullable Validator... validators) {
696
    Object target = getTarget();
3✔
697
    for (Validator validator : validators) {
16✔
698
      if (validator != null && (target != null && !validator.supports(target.getClass()))) {
9!
699
        throw new IllegalStateException("Invalid target for Validator [%s]: %s".formatted(validator, target));
×
700
      }
701
    }
702
  }
1✔
703

704
  /**
705
   * Configure a predicate to exclude validators.
706
   */
707
  public void setExcludedValidators(Predicate<Validator> predicate) {
708
    this.excludedValidators = predicate;
×
709
  }
×
710

711
  /**
712
   * Add Validators to apply after each binding step.
713
   *
714
   * @see #setValidator(Validator)
715
   * @see #replaceValidators(Validator...)
716
   */
717
  public void addValidators(Validator... validators) {
718
    assertValidators(validators);
×
719
    this.validators.addAll(Arrays.asList(validators));
×
720
  }
×
721

722
  /**
723
   * Replace the Validators to apply after each binding step.
724
   *
725
   * @see #setValidator(Validator)
726
   * @see #addValidators(Validator...)
727
   */
728
  public void replaceValidators(Validator... validators) {
729
    assertValidators(validators);
×
730
    this.validators.clear();
×
731
    this.validators.addAll(Arrays.asList(validators));
×
732
  }
×
733

734
  /**
735
   * Return the primary Validator to apply after each binding step, if any.
736
   */
737
  @Nullable
738
  public Validator getValidator() {
739
    return (!this.validators.isEmpty() ? this.validators.get(0) : null);
×
740
  }
741

742
  /**
743
   * Return the Validators to apply after data binding.
744
   */
745
  public List<Validator> getValidators() {
746
    return Collections.unmodifiableList(this.validators);
×
747
  }
748

749
  /**
750
   * Return the Validators to apply after data binding. This includes the
751
   * configured {@link #getValidators() validators} filtered by the
752
   * {@link #setExcludedValidators(Predicate) exclude predicate}.
753
   */
754
  public List<Validator> getValidatorsToApply() {
755
    return (this.excludedValidators != null ?
4!
756
            this.validators.stream().filter(validator -> !this.excludedValidators.test(validator)).toList() :
×
757
            Collections.unmodifiableList(this.validators));
3✔
758
  }
759

760
  //---------------------------------------------------------------------
761
  // Implementation of PropertyEditorRegistry/TypeConverter interface
762
  //---------------------------------------------------------------------
763

764
  /**
765
   * Specify a {@link ConversionService} to use for converting
766
   * property values, as an alternative to JavaBeans PropertyEditors.
767
   */
768
  public void setConversionService(@Nullable ConversionService conversionService) {
769
    Assert.state(this.conversionService == null, "DataBinder is already initialized with ConversionService");
7!
770
    this.conversionService = conversionService;
3✔
771
    if (this.bindingResult != null && conversionService != null) {
3!
772
      this.bindingResult.initConversion(conversionService);
×
773
    }
774
  }
1✔
775

776
  /**
777
   * Return the associated ConversionService, if any.
778
   */
779
  @Nullable
780
  public ConversionService getConversionService() {
781
    return this.conversionService;
3✔
782
  }
783

784
  /**
785
   * Add a custom formatter, applying it to all fields matching the
786
   * {@link Formatter}-declared type.
787
   * <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
788
   *
789
   * @param formatter the formatter to add, generically declared for a specific type
790
   * @see #registerCustomEditor(Class, PropertyEditor)
791
   */
792
  public void addCustomFormatter(Formatter<?> formatter) {
793
    FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
5✔
794
    getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
6✔
795
  }
1✔
796

797
  /**
798
   * Add a custom formatter for the field type specified in {@link Formatter} class,
799
   * applying it to the specified fields only, if any, or otherwise to all fields.
800
   * <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
801
   *
802
   * @param formatter the formatter to add, generically declared for a specific type
803
   * @param fields the fields to apply the formatter to, or none if to be applied to all
804
   * @see #registerCustomEditor(Class, String, PropertyEditor)
805
   */
806
  public void addCustomFormatter(Formatter<?> formatter, String... fields) {
807
    FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
5✔
808
    Class<?> fieldType = adapter.getFieldType();
3✔
809
    PropertyEditorRegistry registry = getPropertyEditorRegistry();
3✔
810
    if (ObjectUtils.isEmpty(fields)) {
3!
811
      registry.registerCustomEditor(fieldType, adapter);
×
812
    }
813
    else {
814
      for (String field : fields) {
16✔
815
        registry.registerCustomEditor(fieldType, field, adapter);
5✔
816
      }
817
    }
818
  }
1✔
819

820
  /**
821
   * Add a custom formatter, applying it to the specified field types only, if any,
822
   * or otherwise to all fields matching the {@link Formatter}-declared type.
823
   * <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
824
   *
825
   * @param formatter the formatter to add (does not need to generically declare a
826
   * field type if field types are explicitly specified as parameters)
827
   * @param fieldTypes the field types to apply the formatter to, or none if to be
828
   * derived from the given {@link Formatter} implementation class
829
   * @see #registerCustomEditor(Class, PropertyEditor)
830
   */
831
  public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes) {
832
    FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
5✔
833
    PropertyEditorRegistry editorRegistry = getPropertyEditorRegistry();
3✔
834
    if (ObjectUtils.isEmpty(fieldTypes)) {
3!
835
      editorRegistry.registerCustomEditor(adapter.getFieldType(), adapter);
×
836
    }
837
    else {
838
      for (Class<?> fieldType : fieldTypes) {
16✔
839
        editorRegistry.registerCustomEditor(fieldType, adapter);
4✔
840
      }
841
    }
842
  }
1✔
843

844
  @Override
845
  public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
846
    getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);
5✔
847
  }
1✔
848

849
  @Override
850
  public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String field, PropertyEditor propertyEditor) {
851
    getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor);
6✔
852
  }
1✔
853

854
  @Override
855
  @Nullable
856
  public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
857
    return getPropertyEditorRegistry().findCustomEditor(requiredType, propertyPath);
×
858
  }
859

860
  @Override
861
  @Nullable
862
  public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException {
863
    return getTypeConverter().convertIfNecessary(value, requiredType);
6✔
864
  }
865

866
  @Override
867
  @Nullable
868
  public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
869
          @Nullable MethodParameter methodParam) throws TypeMismatchException {
870

871
    return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
7✔
872
  }
873

874
  @Override
875
  @Nullable
876
  public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)
877
          throws TypeMismatchException {
878

879
    return getTypeConverter().convertIfNecessary(value, requiredType, field);
×
880
  }
881

882
  @Nullable
883
  @Override
884
  public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
885
          @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
886

887
    return getTypeConverter().convertIfNecessary(value, requiredType, typeDescriptor);
×
888
  }
889

890
  /**
891
   * Create the target with constructor injection of values. It is expected that
892
   * {@link #setTargetType(ResolvableType)} was previously called and that
893
   * {@link #getTarget()} is {@code null}.
894
   * <p>Uses a public, no-arg constructor if available in the target object type,
895
   * also supporting a "primary constructor" approach for data classes as follows:
896
   * It understands the JavaBeans {@code ConstructorProperties} annotation as
897
   * well as runtime-retained parameter names in the bytecode, associating
898
   * input values with constructor arguments by name. If no such constructor is
899
   * found, the default constructor will be used (even if not public), assuming
900
   * subsequent bean property bindings through setter methods.
901
   * <p>After the call, use {@link #getBindingResult()} to check for failures
902
   * to bind to, and/or validate constructor arguments. If there are no errors,
903
   * the target is set, and {@link #doBind(PropertyValues)} can be used
904
   * for further initialization via setters.
905
   *
906
   * @param valueResolver to resolve constructor argument values with
907
   * @throws BeanInstantiationException in case of constructor failure
908
   */
909
  public void construct(ValueResolver valueResolver) {
910
    Assert.state(this.target == null, "Target instance already available");
7!
911
    Assert.state(this.targetType != null, "Target type not set");
7!
912

913
    this.target = createObject(this.targetType, "", valueResolver);
8✔
914

915
    if (!getBindingResult().hasErrors()) {
4✔
916
      this.bindingResult = null;
3✔
917
      if (this.typeConverter != null) {
3✔
918
        this.typeConverter.registerCustomEditors(getPropertyAccessor());
5✔
919
      }
920
    }
921
  }
1✔
922

923
  @Nullable
924
  @SuppressWarnings("NullAway")
925
  private Object createObject(ResolvableType objectType, String nestedPath, ValueResolver valueResolver) {
926
    Class<?> clazz = objectType.resolve();
3✔
927
    boolean isOptional = (clazz == Optional.class);
7✔
928
    clazz = (isOptional ? objectType.resolveGeneric(0) : clazz);
13✔
929
    if (clazz == null) {
2!
930
      throw new IllegalStateException(
×
931
              "Insufficient type information to create instance of " + objectType);
932
    }
933

934
    Object result = null;
2✔
935
    Constructor<?> ctor = BeanUtils.obtainConstructor(clazz);
3✔
936

937
    if (ctor.getParameterCount() == 0) {
3✔
938
      // A single default constructor -> clearly a standard JavaBeans arrangement.
939
      result = BeanUtils.newInstance(ctor);
6✔
940
    }
941
    else {
942
      // A single data class constructor -> resolve constructor arguments from request parameters.
943
      String[] paramNames = BeanUtils.getParameterNames(ctor);
3✔
944
      Class<?>[] paramTypes = ctor.getParameterTypes();
3✔
945
      @Nullable Object[] args = new Object[paramTypes.length];
4✔
946
      HashSet<String> failedParamNames = new HashSet<>(4);
5✔
947

948
      for (int i = 0; i < paramNames.length; i++) {
8✔
949
        MethodParameter param = MethodParameter.forFieldAwareConstructor(ctor, i, paramNames[i]);
7✔
950
        String lookupName = null;
2✔
951
        if (this.nameResolver != null) {
3✔
952
          lookupName = this.nameResolver.resolveName(param);
5✔
953
        }
954
        if (lookupName == null) {
2✔
955
          lookupName = paramNames[i];
4✔
956
        }
957

958
        String paramPath = nestedPath + lookupName;
4✔
959
        Class<?> paramType = paramTypes[i];
4✔
960
        ResolvableType resolvableType = ResolvableType.forMethodParameter(param);
3✔
961

962
        Object value = valueResolver.resolveValue(paramPath, paramType);
5✔
963

964
        if (value == null) {
2✔
965
          if (List.class.isAssignableFrom(paramType)) {
4✔
966
            value = createList(paramPath, paramType, resolvableType, valueResolver);
8✔
967
          }
968
          else if (Map.class.isAssignableFrom(paramType)) {
4✔
969
            value = createMap(paramPath, paramType, resolvableType, valueResolver);
8✔
970
          }
971
          else if (paramType.isArray()) {
3✔
972
            value = createArray(paramPath, paramType, resolvableType, valueResolver);
7✔
973
          }
974
        }
975

976
        if (value == null && shouldConstructArgument(param) && hasValuesFor(paramPath, valueResolver)) {
11✔
977
          args[i] = createObject(resolvableType, paramPath + ".", valueResolver);
10✔
978
        }
979
        else {
980
          try {
981
            if (value == null && (param.isOptional() || getBindingResult().hasErrors())) {
9✔
982
              args[i] = (param.getParameterType() == Optional.class ? Optional.empty() : null);
11✔
983
            }
984
            else {
985
              args[i] = convertIfNecessary(value, paramType, param);
8✔
986
            }
987
          }
988
          catch (TypeMismatchException ex) {
1✔
989
            args[i] = null;
4✔
990
            failedParamNames.add(paramPath);
4✔
991
            handleTypeMismatchException(ex, paramPath, paramType, value);
6✔
992
          }
1✔
993
        }
994
      }
995

996
      if (getBindingResult().hasErrors()) {
4✔
997
        for (int i = 0; i < paramNames.length; i++) {
8✔
998
          String paramPath = nestedPath + paramNames[i];
6✔
999
          if (!failedParamNames.contains(paramPath)) {
4✔
1000
            Object value = args[i];
4✔
1001
            getBindingResult().recordFieldValue(paramPath, paramTypes[i], value);
8✔
1002
            validateConstructorArgument(ctor.getDeclaringClass(), nestedPath, paramNames[i], value);
9✔
1003
          }
1004
        }
1005
        if (!(objectType.getSource() instanceof MethodParameter param && param.isOptional())) {
12✔
1006
          try {
1007
            result = BeanUtils.newInstance(ctor, args);
4✔
1008
          }
1009
          catch (BeanInstantiationException ex) {
×
1010
            // swallow and proceed without target instance
1011
          }
1✔
1012
        }
1013
      }
1014
      else {
1015
        result = BeanUtils.newInstance(ctor, args);
4✔
1016
      }
1017
    }
1018

1019
    return (isOptional && !nestedPath.isEmpty() ? Optional.ofNullable(result) : result);
7!
1020
  }
1021

1022
  /**
1023
   * Whether to instantiate the constructor argument of the given type,
1024
   * matching its own constructor arguments to bind values.
1025
   * <p>By default, simple value types, maps, collections, and arrays are
1026
   * excluded from nested constructor binding initialization.
1027
   */
1028
  protected boolean shouldConstructArgument(MethodParameter param) {
1029
    Class<?> type = param.nestedIfOptional().getNestedParameterType();
4✔
1030
    return !(BeanUtils.isSimpleValueType(type)
6✔
1031
            || Collection.class.isAssignableFrom(type)
4!
1032
            || Map.class.isAssignableFrom(type)
3!
1033
            || type.isArray()
3✔
1034
            || type.getPackageName().startsWith("java."));
7!
1035
  }
1036

1037
  private boolean hasValuesFor(String paramPath, ValueResolver resolver) {
1038
    for (String name : resolver.getNames()) {
11✔
1039
      if (name.startsWith(paramPath + ".")) {
5✔
1040
        return true;
2✔
1041
      }
1042
    }
1✔
1043
    return false;
2✔
1044
  }
1045

1046
  @Nullable
1047
  private List<?> createList(String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
1048
    ResolvableType elementType = type.getNested(2);
4✔
1049
    SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
4✔
1050
    if (indexes == null) {
2!
1051
      return null;
×
1052
    }
1053

1054
    int lastIndex = Math.max(indexes.last(), 0);
7✔
1055
    int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1 : 0);
9!
1056
    List<?> list = (List<?>) CollectionUtils.createCollection(paramType, size);
5✔
1057
    for (int i = 0; i < size; i++) {
7✔
1058
      list.add(null);
4✔
1059
    }
1060

1061
    for (int index : indexes) {
11✔
1062
      String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]";
11✔
1063
      list.set(Math.max(index, 0),
12✔
1064
              createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
1✔
1065
    }
1✔
1066

1067
    return list;
2✔
1068
  }
1069

1070
  @Nullable
1071
  private <V> Map<String, V> createMap(String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
1072
    ResolvableType elementType = type.getNested(2);
4✔
1073
    Map<String, V> map = null;
2✔
1074
    for (String name : valueResolver.getNames()) {
11✔
1075
      if (!name.startsWith(paramPath + "[")) {
5✔
1076
        continue;
1✔
1077
      }
1078

1079
      int startIdx = paramPath.length() + 1;
5✔
1080
      int endIdx = name.indexOf(']', startIdx);
5✔
1081
      boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
21!
1082
      String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
16✔
1083
      if (map == null) {
2✔
1084
        map = CollectionUtils.createMap(paramType, 16);
4✔
1085
      }
1086

1087
      String indexedPath = name.substring(0, endIdx + 1);
7✔
1088
      map.put(key, createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
11✔
1089
    }
1✔
1090

1091
    return map;
2✔
1092
  }
1093

1094
  @Nullable
1095
  @SuppressWarnings("unchecked")
1096
  private <V> V @Nullable [] createArray(String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
1097
    ResolvableType elementType = type.getNested(2);
4✔
1098
    SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
4✔
1099
    if (indexes == null) {
2✔
1100
      return null;
2✔
1101
    }
1102

1103
    int lastIndex = Math.max(indexes.last(), 0);
7✔
1104
    int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1 : 0);
9!
1105
    @Nullable V[] array = (V[]) Array.newInstance(elementType.resolve(), size);
6✔
1106

1107
    for (int index : indexes) {
11✔
1108
      String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]";
10!
1109
      array[Math.max(index, 0)] =
10✔
1110
              createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver);
2✔
1111
    }
1✔
1112

1113
    return array;
2✔
1114
  }
1115

1116
  @Nullable
1117
  private static SortedSet<Integer> getIndexes(String paramPath, ValueResolver valueResolver) {
1118
    SortedSet<Integer> indexes = null;
2✔
1119
    for (String name : valueResolver.getNames()) {
11✔
1120
      if (name.startsWith(paramPath + "[")) {
5✔
1121
        int index;
1122
        if (paramPath.length() + 2 == name.length()) {
7✔
1123
          if (!name.endsWith("[]")) {
4!
1124
            continue;
×
1125
          }
1126
          index = NO_INDEX;
3✔
1127
        }
1128
        else {
1129
          int endIndex = name.indexOf(']', paramPath.length() + 2);
8✔
1130
          String indexValue = name.substring(paramPath.length() + 1, endIndex);
8✔
1131
          index = Integer.parseInt(indexValue);
3✔
1132
        }
1133
        indexes = indexes != null ? indexes : new TreeSet<>();
8✔
1134
        indexes.add(index);
5✔
1135
      }
1136
    }
1✔
1137
    return indexes;
2✔
1138
  }
1139

1140
  @Nullable
1141
  @SuppressWarnings("unchecked")
1142
  private <V> V createIndexedValue(String paramPath, Class<?> containerType,
1143
          ResolvableType elementType, String indexedPath, ValueResolver valueResolver) {
1144

1145
    Object value = null;
2✔
1146
    Class<?> elementClass = elementType.resolve(Object.class);
4✔
1147

1148
    if (List.class.isAssignableFrom(elementClass)) {
4✔
1149
      value = createList(indexedPath, elementClass, elementType, valueResolver);
8✔
1150
    }
1151
    else if (Map.class.isAssignableFrom(elementClass)) {
4✔
1152
      value = createMap(indexedPath, elementClass, elementType, valueResolver);
8✔
1153
    }
1154
    else if (elementClass.isArray()) {
3!
1155
      value = createArray(indexedPath, elementClass, elementType, valueResolver);
×
1156
    }
1157
    else {
1158
      Object rawValue = valueResolver.resolveValue(indexedPath, elementClass);
5✔
1159
      if (rawValue != null) {
2✔
1160
        try {
1161
          value = convertIfNecessary(rawValue, elementClass);
5✔
1162
        }
1163
        catch (TypeMismatchException ex) {
×
1164
          handleTypeMismatchException(ex, paramPath, containerType, rawValue);
×
1165
        }
1✔
1166
      }
1167
      else {
1168
        value = createObject(elementType, indexedPath + ".", valueResolver);
7✔
1169
      }
1170
    }
1171

1172
    return (V) value;
2✔
1173
  }
1174

1175
  private void handleTypeMismatchException(TypeMismatchException ex, String paramPath, Class<?> paramType, @Nullable Object value) {
1176
    ex.initPropertyName(paramPath);
3✔
1177
    getBindingResult().recordFieldValue(paramPath, paramType, value);
6✔
1178
    getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult());
6✔
1179
  }
1✔
1180

1181
  private void validateConstructorArgument(Class<?> constructorClass, String nestedPath, String name, @Nullable Object value) {
1182
    Object[] hints = null;
2✔
1183
    if (targetType != null && targetType.getSource() instanceof MethodParameter parameter) {
13!
1184
      for (Annotation ann : parameter.getParameterAnnotations()) {
15✔
1185
        hints = ValidationAnnotationUtils.determineValidationHints(ann);
3✔
1186
        if (hints != null) {
2!
1187
          break;
1✔
1188
        }
1189
      }
1190
    }
1191
    if (hints == null) {
2✔
1192
      return;
1✔
1193
    }
1194
    for (Validator validator : getValidatorsToApply()) {
11✔
1195
      if (validator instanceof SmartValidator smartValidator) {
6!
1196
        boolean isNested = !nestedPath.isEmpty();
7✔
1197
        if (isNested) {
2✔
1198
          getBindingResult().pushNestedPath(nestedPath.substring(0, nestedPath.length() - 1));
10✔
1199
        }
1200
        try {
1201
          smartValidator.validateValue(constructorClass, name, value, getBindingResult(), hints);
8✔
1202
        }
1203
        catch (IllegalArgumentException ex) {
1✔
1204
          // No corresponding field on the target class...
1205
        }
1✔
1206
        if (isNested) {
2✔
1207
          getBindingResult().popNestedPath();
3✔
1208
        }
1209
      }
1210
    }
1✔
1211
  }
1✔
1212

1213
  /**
1214
   * Bind the given property values to this binder's target.
1215
   * <p>This call can create field errors, representing basic binding
1216
   * errors like a required field (code "required"), or type mismatch
1217
   * between value and bean property (code "typeMismatch").
1218
   * <p>Note that the given PropertyValues should be a throwaway instance:
1219
   * For efficiency, it will be modified to just contain allowed fields if it
1220
   * implements the PropertyValues interface; else, an internal mutable
1221
   * copy will be created for this purpose. Pass in a copy of the PropertyValues
1222
   * if you want your original instance to stay unmodified in any case.
1223
   *
1224
   * @param pvs property values to bind
1225
   * @see #doBind(PropertyValues)
1226
   */
1227
  public void bind(PropertyValues pvs) {
1228
    if (shouldNotBindPropertyValues()) {
3✔
1229
      return;
1✔
1230
    }
1231
    doBind(pvs);
3✔
1232
  }
1✔
1233

1234
  /**
1235
   * Whether to not bind parameters to properties. Returns "true" if
1236
   * {@link #isDeclarativeBinding()} is on, and
1237
   * {@link #setAllowedFields(String...) allowedFields} are not configured.
1238
   */
1239
  protected boolean shouldNotBindPropertyValues() {
1240
    return isDeclarativeBinding() && ObjectUtils.isEmpty(this.allowedFields);
11!
1241
  }
1242

1243
  /**
1244
   * Actual implementation of the binding process, working with the
1245
   * passed-in PropertyValues instance.
1246
   *
1247
   * @param values the property values to bind,
1248
   * as PropertyValues instance
1249
   * @see #checkAllowedFields
1250
   * @see #checkRequiredFields
1251
   * @see #applyPropertyValues
1252
   */
1253
  protected void doBind(PropertyValues values) {
1254
    checkAllowedFields(values);
3✔
1255
    checkRequiredFields(values);
3✔
1256
    applyPropertyValues(values);
3✔
1257
  }
1✔
1258

1259
  /**
1260
   * Check the given property values against the allowed fields,
1261
   * removing values for fields that are not allowed.
1262
   *
1263
   * @param mpvs the property values to be bound (can be modified)
1264
   * @see #getAllowedFields
1265
   * @see #isAllowed(String)
1266
   */
1267
  protected void checkAllowedFields(PropertyValues mpvs) {
1268
    for (PropertyValue pv : mpvs.toArray()) {
17✔
1269
      String field = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
4✔
1270
      if (!isAllowed(field)) {
4✔
1271
        mpvs.remove(pv);
3✔
1272
        getBindingResult().recordSuppressedField(field);
4✔
1273
        if (logger.isDebugEnabled()) {
3!
1274
          logger.debug("Field [{}] has been removed from PropertyValues " +
×
1275
                  "and will not be bound, because it has not been found in the list of allowed fields", field);
1276
        }
1277
      }
1278
    }
1279
  }
1✔
1280

1281
  /**
1282
   * Determine if the given field is allowed for binding.
1283
   * <p>Invoked for each passed-in property value.
1284
   * <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
1285
   * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts),
1286
   * as well as direct equality, in the configured lists of allowed field
1287
   * patterns and disallowed field patterns.
1288
   * <p>Matching against allowed field patterns is case-sensitive; whereas,
1289
   * matching against disallowed field patterns is case-insensitive.
1290
   * <p>A field matching a disallowed pattern will not be accepted even if it
1291
   * also happens to match a pattern in the allowed list.
1292
   * <p>Can be overridden in subclasses, but care must be taken to honor the
1293
   * aforementioned contract.
1294
   *
1295
   * @param field the field to check
1296
   * @return {@code true} if the field is allowed
1297
   * @see #setAllowedFields
1298
   * @see #setDisallowedFields
1299
   * @see PatternMatchUtils#simpleMatch(String, String)
1300
   */
1301
  protected boolean isAllowed(String field) {
1302
    String[] allowed = getAllowedFields();
3✔
1303
    String[] disallowed = getDisallowedFields();
3✔
1304
    if (ObjectUtils.isNotEmpty(allowed) && !PatternMatchUtils.simpleMatch(allowed, field)) {
7✔
1305
      return false;
2✔
1306
    }
1307
    if (ObjectUtils.isNotEmpty(disallowed)) {
3✔
1308
      return !PatternMatchUtils.simpleMatchIgnoreCase(disallowed, field);
8✔
1309
    }
1310
    return true;
2✔
1311
  }
1312

1313
  /**
1314
   * Check the given property values against the required fields,
1315
   * generating missing field errors where appropriate.
1316
   *
1317
   * @param mpvs the property values to be bound (can be modified)
1318
   * @see #getRequiredFields
1319
   * @see #getBindingErrorProcessor
1320
   * @see BindingErrorProcessor#processMissingFieldError
1321
   */
1322
  @SuppressWarnings("NullAway")
1323
  protected void checkRequiredFields(PropertyValues mpvs) {
1324
    String[] requiredFields = getRequiredFields();
3✔
1325
    if (ObjectUtils.isNotEmpty(requiredFields)) {
3✔
1326
      HashMap<String, PropertyValue> propertyValues = new HashMap<>();
4✔
1327
      for (PropertyValue pv : mpvs) {
10✔
1328
        String canonicalName = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
4✔
1329
        propertyValues.put(canonicalName, pv);
5✔
1330
      }
1✔
1331

1332
      BindingErrorProcessor bindingErrorProcessor = getBindingErrorProcessor();
3✔
1333
      for (String field : requiredFields) {
16✔
1334
        PropertyValue pv = propertyValues.get(field);
5✔
1335
        boolean empty = pv == null || pv.getValue() == null;
9✔
1336
        if (!empty) {
2✔
1337
          if (pv.getValue() instanceof String text) {
9✔
1338
            empty = StringUtils.isBlank(text);
4✔
1339
          }
1340
          else if (pv.getValue() instanceof String[] values) {
9!
1341
            empty = values.length == 0 || StringUtils.isBlank(values[0]);
10!
1342
          }
1343
        }
1344
        if (empty) {
2✔
1345
          // Use bind error processor to create FieldError.
1346
          bindingErrorProcessor.processMissingFieldError(field, getInternalBindingResult());
5✔
1347
          // Remove property from property values to bind:
1348
          // It has already caused a field error with a rejected value.
1349
          if (pv != null) {
2✔
1350
            mpvs.remove(pv);
3✔
1351
            propertyValues.remove(field);
4✔
1352
          }
1353
        }
1354
      }
1355
    }
1356
  }
1✔
1357

1358
  /**
1359
   * Apply given property values to the target object.
1360
   * <p>Default implementation applies all the supplied property
1361
   * values as bean property values. By default, unknown fields will
1362
   * be ignored.
1363
   *
1364
   * @param mpvs the property values to be bound (can be modified)
1365
   * @see #getTarget
1366
   * @see #getPropertyAccessor
1367
   * @see #isIgnoreUnknownFields
1368
   * @see #getBindingErrorProcessor
1369
   * @see BindingErrorProcessor#processPropertyAccessException
1370
   */
1371
  protected void applyPropertyValues(PropertyValues mpvs) {
1372
    try {
1373
      // Bind request parameters onto target object.
1374
      getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
8✔
1375
    }
1376
    catch (PropertyBatchUpdateException ex) {
1✔
1377
      // Use bind error processor to create FieldErrors.
1378
      for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
17✔
1379
        getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
6✔
1380
      }
1381
    }
1✔
1382
  }
1✔
1383

1384
  /**
1385
   * Invoke the specified Validators, if any.
1386
   *
1387
   * @see #setValidator(Validator)
1388
   * @see #getBindingResult()
1389
   */
1390
  public void validate() {
1391
    Object target = getTarget();
×
1392
    Assert.state(target != null, "No target to validate");
×
1393
    BindingResult bindingResult = getBindingResult();
×
1394
    // Call each validator with the same binding result
1395
    for (Validator validator : getValidatorsToApply()) {
×
1396
      validator.validate(target, bindingResult);
×
1397
    }
×
1398
  }
×
1399

1400
  /**
1401
   * Invoke the specified Validators, if any, with the given validation hints.
1402
   * <p>Note: Validation hints may get ignored by the actual target Validator.
1403
   *
1404
   * @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
1405
   * @see #setValidator(Validator)
1406
   * @see SmartValidator#validate(Object, Errors, Object...)
1407
   */
1408
  public void validate(Object... validationHints) {
1409
    Object target = getTarget();
3✔
1410
    Assert.state(target != null, "No target to validate");
6!
1411
    BindingResult bindingResult = getBindingResult();
3✔
1412
    // Call each validator with the same binding result
1413
    for (Validator validator : getValidatorsToApply()) {
11✔
1414
      if (ObjectUtils.isNotEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
3!
1415
        smartValidator.validate(target, bindingResult, validationHints);
×
1416
      }
1417
      else if (validator != null) {
2!
1418
        validator.validate(target, bindingResult);
4✔
1419
      }
1420
    }
1✔
1421
  }
1✔
1422

1423
  /**
1424
   * Close this DataBinder, which may result in throwing
1425
   * a BindException if it encountered any errors.
1426
   *
1427
   * @return the model Map, containing target object and Errors instance
1428
   * @throws BindException if there were any errors in the bind operation
1429
   * @see BindingResult#getModel()
1430
   */
1431
  public Map<?, ?> close() throws BindException {
1432
    if (getBindingResult().hasErrors()) {
4✔
1433
      throw new BindException(getBindingResult());
6✔
1434
    }
1435
    return getBindingResult().getModel();
4✔
1436
  }
1437

1438
  /**
1439
   * Strategy to determine the name of the value to bind to a method parameter.
1440
   * Supported on constructor parameters with {@link #construct constructor
1441
   * binding} which performs lookups via {@link ValueResolver#resolveValue}.
1442
   */
1443
  public interface NameResolver {
1444

1445
    /**
1446
     * Return the name to use for the given method parameter, or {@code null}
1447
     * if unresolved. For constructor parameters, the name is determined via
1448
     * {@link DefaultParameterNameDiscoverer} if
1449
     * unresolved.
1450
     */
1451
    @Nullable
1452
    String resolveName(MethodParameter parameter);
1453

1454
  }
1455

1456
  /**
1457
   * Strategy for {@link #construct constructor binding} to look up the values
1458
   * to bind to a given constructor parameter.
1459
   */
1460
  public interface ValueResolver {
1461

1462
    /**
1463
     * Resolve the value for the given name and target parameter type.
1464
     *
1465
     * @param name the name to use for the lookup, possibly a nested path
1466
     * for constructor parameters on nested objects
1467
     * @param type the target type, based on the constructor parameter type
1468
     * @return the resolved value, possibly {@code null} if none found
1469
     */
1470
    @Nullable
1471
    Object resolveValue(String name, Class<?> type);
1472

1473
    /**
1474
     * Return the names of all property values.
1475
     * <p>Useful for proactive checks whether there are property values nested
1476
     * further below the path for a constructor arg. If not then the
1477
     * constructor arg can be considered missing and not to be instantiated.
1478
     */
1479
    Set<String> getNames();
1480
  }
1481

1482
  /**
1483
   * {@link SimpleTypeConverter} that is also {@link PropertyEditorRegistrar}.
1484
   */
1485
  private static final class ExtendedTypeConverter extends SimpleTypeConverter implements PropertyEditorRegistrar {
1486

1487
    @Override
1488
    public void registerCustomEditors(PropertyEditorRegistry registry) {
1489
      copyCustomEditorsTo(registry, null);
4✔
1490
    }
1✔
1491
  }
1492

1493
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc