1+ /*
2+ * Copyright 2020 the original author or authors.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * https://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+ package org .springframework .batch .item .file .mapping ;
17+
18+ import java .lang .reflect .Constructor ;
19+
20+ import org .springframework .batch .item .file .transform .FieldSet ;
21+ import org .springframework .beans .BeanUtils ;
22+ import org .springframework .beans .SimpleTypeConverter ;
23+ import org .springframework .core .convert .ConversionService ;
24+ import org .springframework .core .convert .support .DefaultConversionService ;
25+ import org .springframework .util .Assert ;
26+
27+ /**
28+ * This is a {@link FieldSetMapper} that supports Java records mapping.
29+ * It uses the record's canonical constructor to map components with the
30+ * same name as tokens in the {@link FieldSet}.
31+ *
32+ * @param <T> type of mapped items
33+ * @author Mahmoud Ben Hassine
34+ * @since 4.3
35+ */
36+ public class RecordFieldSetMapper <T > implements FieldSetMapper <T > {
37+
38+ private final SimpleTypeConverter typeConverter = new SimpleTypeConverter ();
39+ private final Constructor <T > mappedConstructor ;
40+ private String [] constructorParameterNames ;
41+ private Class <?>[] constructorParameterTypes ;
42+
43+ /**
44+ * Create a new {@link RecordFieldSetMapper}.
45+ *
46+ * @param targetType type of mapped items
47+ */
48+ public RecordFieldSetMapper (Class <T > targetType ) {
49+ this (targetType , new DefaultConversionService ());
50+ }
51+
52+ /**
53+ * Create a new {@link RecordFieldSetMapper}.
54+ *
55+ * @param targetType type of mapped items
56+ * @param conversionService service to use to convert raw data to typed fields
57+ */
58+ public RecordFieldSetMapper (Class < T > targetType , ConversionService conversionService ) {
59+ this .typeConverter .setConversionService (conversionService );
60+ this .mappedConstructor = BeanUtils .getResolvableConstructor (targetType );
61+ if (this .mappedConstructor .getParameterCount () > 0 ) {
62+ this .constructorParameterNames = BeanUtils .getParameterNames (this .mappedConstructor );
63+ this .constructorParameterTypes = this .mappedConstructor .getParameterTypes ();
64+ }
65+ }
66+
67+ @ Override
68+ public T mapFieldSet (FieldSet fieldSet ) {
69+ Assert .isTrue (fieldSet .getFieldCount () == this .constructorParameterNames .length ,
70+ "Fields count must be equal to record components count" );
71+ Assert .isTrue (fieldSet .hasNames (), "Field names must specified" );
72+ Object [] args = new Object [0 ];
73+ if (this .constructorParameterNames != null && this .constructorParameterTypes != null ) {
74+ args = new Object [this .constructorParameterNames .length ];
75+ for (int i = 0 ; i < args .length ; i ++) {
76+ String name = this .constructorParameterNames [i ];
77+ Class <?> type = this .constructorParameterTypes [i ];
78+ args [i ] = this .typeConverter .convertIfNecessary (fieldSet .readRawString (name ), type );
79+ }
80+ }
81+ return BeanUtils .instantiateClass (this .mappedConstructor , args );
82+ }
83+ }
0 commit comments