• 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

84.35
today-context/src/main/java/infra/format/support/ApplicationConversionService.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.format.support;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.annotation.Annotation;
23
import java.util.LinkedHashMap;
24
import java.util.Map;
25
import java.util.Optional;
26
import java.util.Set;
27
import java.util.function.BiFunction;
28
import java.util.function.Consumer;
29
import java.util.function.Supplier;
30

31
import infra.beans.factory.BeanFactory;
32
import infra.beans.factory.annotation.BeanFactoryAnnotationUtils;
33
import infra.beans.factory.config.BeanDefinition;
34
import infra.beans.factory.config.ConfigurableBeanFactory;
35
import infra.context.ConfigurableApplicationContext;
36
import infra.core.ResolvableType;
37
import infra.core.StringValueResolver;
38
import infra.core.TypeDescriptor;
39
import infra.core.conversion.ConditionalConverter;
40
import infra.core.conversion.ConditionalGenericConverter;
41
import infra.core.conversion.ConversionService;
42
import infra.core.conversion.Converter;
43
import infra.core.conversion.ConverterFactory;
44
import infra.core.conversion.ConverterRegistry;
45
import infra.core.conversion.GenericConverter;
46
import infra.core.conversion.GenericConverter.ConvertiblePair;
47
import infra.core.conversion.support.ConfigurableConversionService;
48
import infra.core.conversion.support.DefaultConversionService;
49
import infra.core.i18n.LocaleContextHolder;
50
import infra.format.AnnotationFormatterFactory;
51
import infra.format.Formatter;
52
import infra.format.FormatterRegistry;
53
import infra.format.Parser;
54
import infra.format.Printer;
55
import infra.lang.Assert;
56
import infra.util.StringUtils;
57

58
/**
59
 * A specialization of {@link FormattingConversionService} configured by default with
60
 * converters and formatters appropriate for most applications.
61
 * <p>
62
 * Designed for direct instantiation but also exposes the static
63
 * {@link #addApplicationConverters} and
64
 * {@link #addApplicationFormatters(FormatterRegistry)} utility methods for ad-hoc use
65
 * against registry instance.
66
 *
67
 * @author Phillip Webb
68
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
69
 * @since 4.0
70
 */
71
public class ApplicationConversionService extends FormattingConversionService {
72

73
  private static final ResolvableType STRING = ResolvableType.forClass(String.class);
4✔
74

75
  @Nullable
76
  private static volatile ApplicationConversionService sharedInstance;
77

78
  private final boolean unmodifiable;
79

80
  public ApplicationConversionService() {
81
    this(null);
3✔
82
  }
1✔
83

84
  public ApplicationConversionService(@Nullable StringValueResolver embeddedValueResolver) {
85
    this(embeddedValueResolver, false);
4✔
86
  }
1✔
87

88
  private ApplicationConversionService(@Nullable StringValueResolver embeddedValueResolver, boolean unmodifiable) {
2✔
89
    if (embeddedValueResolver != null) {
2!
90
      setEmbeddedValueResolver(embeddedValueResolver);
×
91
    }
92
    configure(this);
2✔
93
    this.unmodifiable = unmodifiable;
3✔
94
  }
1✔
95

96
  @Override
97
  public void addPrinter(Printer<?> printer) {
98
    assertModifiable();
×
99
    super.addPrinter(printer);
×
100
  }
×
101

102
  @Override
103
  public void addParser(Parser<?> parser) {
104
    assertModifiable();
×
105
    super.addParser(parser);
×
106
  }
×
107

108
  @Override
109
  public void addFormatter(Formatter<?> formatter) {
110
    assertModifiable();
2✔
111
    super.addFormatter(formatter);
3✔
112
  }
1✔
113

114
  @Override
115
  public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
116
    assertModifiable();
2✔
117
    super.addFormatterForFieldType(fieldType, formatter);
4✔
118
  }
1✔
119

120
  @Override
121
  public void addConverter(Converter<?, ?> converter) {
122
    assertModifiable();
2✔
123
    super.addConverter(converter);
3✔
124
  }
1✔
125

126
  @Override
127
  public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
128
    assertModifiable();
2✔
129
    super.addFormatterForFieldType(fieldType, printer, parser);
5✔
130
  }
1✔
131

132
  @Override
133
  public <T extends Annotation> void addFormatterForFieldAnnotation(AnnotationFormatterFactory<T> factory) {
134
    assertModifiable();
2✔
135
    super.addFormatterForFieldAnnotation(factory);
3✔
136
  }
1✔
137

138
  @Override
139
  public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
140
    assertModifiable();
2✔
141
    super.addConverter(sourceType, targetType, converter);
5✔
142
  }
1✔
143

144
  @Override
145
  public void addConverter(GenericConverter converter) {
146
    assertModifiable();
2✔
147
    super.addConverter(converter);
3✔
148
  }
1✔
149

150
  @Override
151
  public void addConverterFactory(ConverterFactory<?, ?> factory) {
152
    assertModifiable();
2✔
153
    super.addConverterFactory(factory);
3✔
154
  }
1✔
155

156
  @Override
157
  public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
158
    assertModifiable();
×
159
    super.removeConvertible(sourceType, targetType);
×
160
  }
×
161

162
  private void assertModifiable() {
163
    if (this.unmodifiable) {
3✔
164
      throw new UnsupportedOperationException("This ApplicationConversionService cannot be modified");
5✔
165
    }
166
  }
1✔
167

168
  /**
169
   * Return {@code true} if objects of {@code sourceType} can be converted to the
170
   * {@code targetType} and the converter has {@code Object.class} as a supported source
171
   * type.
172
   *
173
   * @param sourceType the source type to test
174
   * @param targetType the target type to test
175
   * @return if conversion happens through an {@code ObjectTo...} converter
176
   */
177
  public boolean isConvertViaObjectSourceType(TypeDescriptor sourceType, TypeDescriptor targetType) {
178
    GenericConverter converter = getConverter(sourceType, targetType);
5✔
179
    Set<ConvertiblePair> pairs = converter != null ? converter.getConvertibleTypes() : null;
6!
180
    if (pairs != null) {
2!
181
      for (ConvertiblePair pair : pairs) {
10✔
182
        if (Object.class.equals(pair.sourceType)) {
5✔
183
          return true;
2✔
184
        }
185
      }
1✔
186
    }
187
    return false;
2✔
188
  }
189

190
  /**
191
   * Return a shared default application {@code ConversionService} instance, lazily
192
   * building it once needed.
193
   * <p>
194
   * Note: This method actually returns an {@link ApplicationConversionService}
195
   * instance. However, the {@code ConversionService} signature has been preserved for
196
   * binary compatibility.
197
   *
198
   * @return the shared {@code ApplicationConversionService} instance (never
199
   * {@code null})
200
   */
201
  public static ApplicationConversionService getSharedInstance() {
202
    ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
2✔
203
    if (sharedInstance == null) {
2✔
204
      synchronized(ApplicationConversionService.class) {
4✔
205
        sharedInstance = ApplicationConversionService.sharedInstance;
2✔
206
        if (sharedInstance == null) {
2!
207
          sharedInstance = new ApplicationConversionService(null, true);
6✔
208
          ApplicationConversionService.sharedInstance = sharedInstance;
2✔
209
        }
210
      }
3✔
211
    }
212
    return sharedInstance;
2✔
213
  }
214

215
  /**
216
   * Configure the given {@link FormatterRegistry} with formatters and converters
217
   * appropriate for most infra applications.
218
   *
219
   * @param registry the registry of converters to add to (must also be castable to
220
   * ConversionService, e.g. being a {@link ConfigurableConversionService})
221
   * @throws ClassCastException if the given FormatterRegistry could not be cast to a
222
   * ConversionService
223
   */
224
  public static void configure(FormatterRegistry registry) {
225
    DefaultConversionService.addDefaultConverters(registry);
2✔
226
    DefaultFormattingConversionService.addDefaultFormatters(registry);
2✔
227
    addApplicationFormatters(registry);
2✔
228
    addApplicationConverters(registry);
2✔
229
  }
1✔
230

231
  /**
232
   * Add converters useful for most infra applications.
233
   *
234
   * @param registry the registry of converters to add to (must also be castable to
235
   * ConversionService, e.g. being a {@link ConfigurableConversionService})
236
   * @throws ClassCastException if the given ConverterRegistry could not be cast to a
237
   * ConversionService
238
   */
239
  public static void addApplicationConverters(ConverterRegistry registry) {
240
    addDelimitedStringConverters(registry);
2✔
241
    registry.addConverter(new StringToDurationConverter());
5✔
242
    registry.addConverter(new DurationToStringConverter());
5✔
243
    registry.addConverter(new NumberToDurationConverter());
5✔
244
    registry.addConverter(new DurationToNumberConverter());
5✔
245
    registry.addConverter(new StringToPeriodConverter());
5✔
246
    registry.addConverter(new PeriodToStringConverter());
5✔
247
    registry.addConverter(new NumberToPeriodConverter());
5✔
248
    registry.addConverter(new StringToDataSizeConverter());
5✔
249
    registry.addConverter(new NumberToDataSizeConverter());
5✔
250
    registry.addConverter(new StringToFileConverter());
5✔
251
    registry.addConverter(new InputStreamSourceToByteArrayConverter());
5✔
252
    registry.addConverterFactory(new LenientStringToEnumConverterFactory());
5✔
253
    registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
5✔
254
    if (registry instanceof ConversionService conversionService) {
6!
255
      addApplicationConverters(registry, conversionService);
3✔
256
    }
257
  }
1✔
258

259
  private static void addApplicationConverters(ConverterRegistry registry, ConversionService conversionService) {
260
    registry.addConverter(new CharSequenceToObjectConverter(conversionService));
6✔
261
  }
1✔
262

263
  /**
264
   * Add converters to support delimited strings.
265
   *
266
   * @param registry the registry of converters to add to (must also be castable to
267
   * ConversionService, e.g. being a {@link ConfigurableConversionService})
268
   * @throws ClassCastException if the given ConverterRegistry could not be cast to a
269
   * ConversionService
270
   */
271
  public static void addDelimitedStringConverters(ConverterRegistry registry) {
272
    ConversionService service = (ConversionService) registry;
3✔
273
    registry.addConverter(new ArrayToDelimitedStringConverter(service));
6✔
274
    registry.addConverter(new CollectionToDelimitedStringConverter(service));
6✔
275
    registry.addConverter(new DelimitedStringToArrayConverter(service));
6✔
276
    registry.addConverter(new DelimitedStringToCollectionConverter(service));
6✔
277
  }
1✔
278

279
  /**
280
   * Add formatters useful for most Infra applications.
281
   *
282
   * @param registry the service to register default formatters with
283
   */
284
  public static void addApplicationFormatters(FormatterRegistry registry) {
285
    registry.addFormatter(new CharArrayFormatter());
5✔
286
    registry.addFormatter(new InetAddressFormatter());
5✔
287
    registry.addFormatter(new IsoOffsetFormatter());
5✔
288
  }
1✔
289

290
  /**
291
   * Add {@link Printer}, {@link Parser}, {@link Formatter}, {@link Converter},
292
   * {@link ConverterFactory}, {@link GenericConverter}, and beans from the specified
293
   * bean factory.
294
   *
295
   * @param registry the service to register beans with
296
   * @param beanFactory the bean factory to get the beans from
297
   */
298
  public static void addBeans(FormatterRegistry registry, BeanFactory beanFactory) {
299
    addBeans(registry, beanFactory, null);
5✔
300
  }
1✔
301

302
  /**
303
   * Add {@link Printer}, {@link Parser}, {@link Formatter}, {@link Converter},
304
   * {@link ConverterFactory}, {@link GenericConverter}, and beans from the specified
305
   * bean factory.
306
   *
307
   * @param registry the service to register beans with
308
   * @param beanFactory the bean factory to get the beans from
309
   * @param qualifier the qualifier required on the beans or {@code null}
310
   * @return the beans that were added
311
   * @since 5.0
312
   */
313
  public static Map<String, Object> addBeans(FormatterRegistry registry, BeanFactory beanFactory, @Nullable String qualifier) {
314
    ConfigurableBeanFactory configurableBeanFactory = getConfigurableListableBeanFactory(beanFactory);
3✔
315
    Map<String, Object> beans = getBeans(beanFactory, qualifier);
4✔
316
    beans.forEach((beanName, bean) -> {
5✔
317
      BeanDefinition beanDefinition = (configurableBeanFactory != null)
2!
318
              ? configurableBeanFactory.getMergedBeanDefinition(beanName) : null;
5✔
319
      ResolvableType type = (beanDefinition != null) ? beanDefinition.getResolvableType() : null;
6!
320
      addBean(registry, bean, type);
4✔
321
    });
1✔
322
    return beans;
2✔
323
  }
324

325
  @Nullable
326
  private static ConfigurableBeanFactory getConfigurableListableBeanFactory(BeanFactory beanFactory) {
327
    if (beanFactory instanceof ConfigurableApplicationContext applicationContext) {
6✔
328
      return applicationContext.getBeanFactory();
3✔
329
    }
330
    if (beanFactory instanceof ConfigurableBeanFactory configurableListableBeanFactory) {
6!
331
      return configurableListableBeanFactory;
2✔
332
    }
333
    return null;
×
334
  }
335

336
  private static Map<String, Object> getBeans(BeanFactory beanFactory, @Nullable String qualifier) {
337
    Map<String, Object> beans = new LinkedHashMap<>();
4✔
338
    beans.putAll(getBeans(beanFactory, Printer.class, qualifier));
6✔
339
    beans.putAll(getBeans(beanFactory, Parser.class, qualifier));
6✔
340
    beans.putAll(getBeans(beanFactory, Formatter.class, qualifier));
6✔
341
    beans.putAll(getBeans(beanFactory, Converter.class, qualifier));
6✔
342
    beans.putAll(getBeans(beanFactory, ConverterFactory.class, qualifier));
6✔
343
    beans.putAll(getBeans(beanFactory, GenericConverter.class, qualifier));
6✔
344
    return beans;
2✔
345
  }
346

347
  private static <T> Map<String, T> getBeans(BeanFactory beanFactory, Class<T> type, @Nullable String qualifier) {
348
    return StringUtils.isEmpty(qualifier) ? beanFactory.getBeansOfType(type)
8✔
349
            : BeanFactoryAnnotationUtils.qualifiedBeansOfType(beanFactory, type, qualifier);
4✔
350
  }
351

352
  static void addBean(FormatterRegistry registry, Object bean, @Nullable ResolvableType beanType) {
353
    if (bean instanceof GenericConverter converterBean) {
6✔
354
      addBean(registry, converterBean, beanType, GenericConverter.class, registry::addConverter, (Runnable) null);
13✔
355
    }
356
    else if (bean instanceof Converter<?, ?> converterBean) {
6✔
357
      Assert.state(beanType != null, "beanType is missing");
6!
358
      addBean(registry, converterBean, beanType, Converter.class, registry::addConverter,
12✔
359
              ConverterBeanAdapter::new);
360
    }
361
    else if (bean instanceof ConverterFactory<?, ?> converterBean) {
6✔
362
      Assert.state(beanType != null, "beanType is missing");
6!
363
      addBean(registry, converterBean, beanType, ConverterFactory.class, registry::addConverterFactory,
12✔
364
              ConverterFactoryBeanAdapter::new);
365
    }
366
    else if (bean instanceof Formatter<?> formatterBean) {
6✔
367
      Assert.state(beanType != null, "beanType is missing");
6!
368
      addBean(registry, formatterBean, beanType, Formatter.class, registry::addFormatter, () -> {
15✔
369
        registry.addConverter(new PrinterBeanAdapter(formatterBean, beanType));
7✔
370
        registry.addConverter(new ParserBeanAdapter(formatterBean, beanType));
7✔
371
      });
1✔
372
    }
373
    else if (bean instanceof Printer<?> printerBean) {
6✔
374
      Assert.state(beanType != null, "beanType is missing");
6!
375
      addBean(registry, printerBean, beanType, Printer.class, registry::addPrinter, PrinterBeanAdapter::new);
12✔
376
    }
377
    else if (bean instanceof Parser<?> parserBean) {
6!
378
      Assert.state(beanType != null, "beanType is missing");
6!
379
      addBean(registry, parserBean, beanType, Parser.class, registry::addParser, ParserBeanAdapter::new);
11✔
380
    }
381
  }
1✔
382

383
  private static <B, T> void addBean(FormatterRegistry registry, B bean, ResolvableType beanType, Class<T> type,
384
          Consumer<B> standardRegistrar, BiFunction<B, ResolvableType, BeanAdapter<?>> beanAdapterFactory) {
385
    addBean(registry, bean, beanType, type, standardRegistrar,
11✔
386
            () -> registry.addConverter(beanAdapterFactory.apply(bean, beanType)));
8✔
387
  }
1✔
388

389
  private static <B, T> void addBean(FormatterRegistry registry, B bean, @Nullable ResolvableType beanType, Class<T> type,
390
          Consumer<B> standardRegistrar, @Nullable Runnable beanAdapterRegistrar) {
391
    if (beanType != null && beanAdapterRegistrar != null
5!
392
            && ResolvableType.forInstance(bean).as(type).hasUnresolvableGenerics()) {
5✔
393
      beanAdapterRegistrar.run();
2✔
394
      return;
1✔
395
    }
396
    standardRegistrar.accept(bean);
3✔
397
  }
1✔
398

399
  /**
400
   * Base class for adapters that adapt a bean to a {@link GenericConverter}.
401
   *
402
   * @param <B> the base type of the bean
403
   */
404
  abstract static class BeanAdapter<B> implements ConditionalGenericConverter {
405

406
    private final B bean;
407

408
    private final ResolvableTypePair types;
409

410
    BeanAdapter(B bean, ResolvableType beanType) {
2✔
411
      Assert.isInstanceOf(beanType.toClass(), bean);
4✔
412
      ResolvableType type = ResolvableType.forClass(getClass()).as(BeanAdapter.class).getGeneric();
9✔
413
      ResolvableType[] generics = beanType.as(type.toClass()).getGenerics();
6✔
414
      this.bean = bean;
3✔
415
      this.types = getResolvableTypePair(generics);
5✔
416
    }
1✔
417

418
    protected ResolvableTypePair getResolvableTypePair(ResolvableType[] generics) {
419
      return new ResolvableTypePair(generics[0], generics[1]);
10✔
420
    }
421

422
    protected B bean() {
423
      return this.bean;
3✔
424
    }
425

426
    @Override
427
    public Set<ConvertiblePair> getConvertibleTypes() {
428
      return Set.of(new ConvertiblePair(this.types.source().toClass(), this.types.target().toClass()));
13✔
429
    }
430

431
    @Override
432
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
433
      return (this.types.target().toClass() == targetType.getObjectType()
10!
434
              && matchesTargetType(targetType.getResolvableType()));
5!
435
    }
436

437
    private boolean matchesTargetType(ResolvableType targetType) {
438
      ResolvableType ours = this.types.target();
4✔
439
      return targetType.getType() instanceof Class || targetType.isAssignableFrom(ours)
11!
440
              || this.types.target().hasUnresolvableGenerics();
5!
441
    }
442

443
    protected final boolean conditionalConverterCandidateMatches(Object candidate, TypeDescriptor sourceType, TypeDescriptor targetType) {
444
      return !(candidate instanceof ConditionalConverter conditionalConverter)
7✔
445
              || conditionalConverter.matches(sourceType, targetType);
8✔
446
    }
447

448
    @Nullable
449
    @SuppressWarnings({ "unchecked", "rawtypes" })
450
    protected final Object convert(@Nullable Object source, TypeDescriptor targetType, Converter<?, ?> converter) {
451
      return (source != null) ? ((Converter) converter).convert(source) : convertNull(targetType);
7!
452
    }
453

454
    @Nullable
455
    private Object convertNull(TypeDescriptor targetType) {
456
      return (targetType.getObjectType() != Optional.class) ? null : Optional.empty();
×
457
    }
458

459
    @Override
460
    public String toString() {
461
      return this.types + " : " + this.bean;
×
462
    }
463

464
  }
465

466
  /**
467
   * Adapts a {@link Printer} bean to a {@link GenericConverter}.
468
   */
469
  static class PrinterBeanAdapter extends BeanAdapter<Printer<?>> {
470

471
    PrinterBeanAdapter(Printer<?> bean, ResolvableType beanType) {
472
      super(bean, beanType);
4✔
473
    }
1✔
474

475
    @Override
476
    protected ResolvableTypePair getResolvableTypePair(ResolvableType[] generics) {
477
      return new ResolvableTypePair(generics[0], STRING);
8✔
478
    }
479

480
    @Override
481
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
482
      return (source != null) ? print(source) : "";
7!
483
    }
484

485
    @SuppressWarnings("unchecked")
486
    private String print(Object object) {
487
      return ((Printer<Object>) bean()).print(object, LocaleContextHolder.getLocale());
7✔
488
    }
489

490
  }
491

492
  /**
493
   * Adapts a {@link Parser} bean to a {@link GenericConverter}.
494
   */
495
  static class ParserBeanAdapter extends BeanAdapter<Parser<?>> {
496

497
    ParserBeanAdapter(Parser<?> bean, ResolvableType beanType) {
498
      super(bean, beanType);
4✔
499
    }
1✔
500

501
    @Override
502
    protected ResolvableTypePair getResolvableTypePair(ResolvableType[] generics) {
503
      return new ResolvableTypePair(STRING, generics[0]);
8✔
504
    }
505

506
    @Nullable
507
    @Override
508
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
509
      String text = (String) source;
3✔
510
      return (!StringUtils.hasText(text)) ? null : parse(text);
7!
511
    }
512

513
    private Object parse(String text) {
514
      try {
515
        return bean().parse(text, LocaleContextHolder.getLocale());
7✔
516
      }
517
      catch (IllegalArgumentException ex) {
×
518
        throw ex;
×
519
      }
520
      catch (Throwable ex) {
×
521
        throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
×
522
      }
523
    }
524

525
  }
526

527
  /**
528
   * Adapts a {@link Converter} bean to a {@link GenericConverter}.
529
   */
530
  static final class ConverterBeanAdapter extends BeanAdapter<Converter<?, ?>> {
531

532
    ConverterBeanAdapter(Converter<?, ?> bean, ResolvableType beanType) {
533
      super(bean, beanType);
4✔
534
    }
1✔
535

536
    @Override
537
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
538
      return super.matches(sourceType, targetType)
8!
539
              && conditionalConverterCandidateMatches(bean(), sourceType, targetType);
8✔
540
    }
541

542
    @Nullable
543
    @Override
544
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
545
      return convert(source, targetType, bean());
8✔
546
    }
547

548
  }
549

550
  /**
551
   * Adapts a {@link ConverterFactory} bean to a {@link GenericConverter}.
552
   */
553
  private static final class ConverterFactoryBeanAdapter extends BeanAdapter<ConverterFactory<?, ?>> {
554

555
    ConverterFactoryBeanAdapter(ConverterFactory<?, ?> bean, ResolvableType beanType) {
556
      super(bean, beanType);
4✔
557
    }
1✔
558

559
    @Override
560
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
561
      return super.matches(sourceType, targetType)
8!
562
              && conditionalConverterCandidateMatches(bean(), sourceType, targetType)
9!
563
              && conditionalConverterCandidateMatches(getConverter(targetType::getType), sourceType, targetType);
10!
564
    }
565

566
    @Nullable
567
    @Override
568
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
569
      return convert(source, targetType, getConverter(targetType::getObjectType));
12✔
570
    }
571

572
    @SuppressWarnings({ "unchecked", "rawtypes" })
573
    private Converter<Object, ?> getConverter(Supplier<Class<?>> typeSupplier) {
574
      return ((ConverterFactory) bean()).getConverter(typeSupplier.get());
8✔
575
    }
576

577
  }
578

579
  /**
580
   * Convertible type information as extracted from bean generics.
581
   *
582
   * @param source the source type
583
   * @param target the target type
584
   */
585
  record ResolvableTypePair(ResolvableType source, ResolvableType target) {
586

587
    ResolvableTypePair {
8✔
588
      Assert.notNull(source.resolve(), "'source' cannot be resolved");
4✔
589
      Assert.notNull(target.resolve(), "'target' cannot be resolved");
4✔
590
    }
1✔
591

592
    @Override
593
    public final String toString() {
594
      return source() + " -> " + target();
×
595
    }
596

597
  }
598

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