View Javadoc
1   package com.opencsv.bean;
2   
3   import org.apache.commons.collections4.ListValuedMap;
4   import org.apache.commons.lang3.StringUtils;
5   
6   import java.lang.annotation.Annotation;
7   import java.lang.reflect.Field;
8   import java.util.*;
9   
10  /*
11   * Copyright 2007 Kyle Miller.
12   *
13   * Licensed under the Apache License, Version 2.0 (the "License");
14   * you may not use this file except in compliance with the License.
15   * You may obtain a copy of the License at
16   *
17   * http://www.apache.org/licenses/LICENSE-2.0
18   *
19   * Unless required by applicable law or agreed to in writing, software
20   * distributed under the License is distributed on an "AS IS" BASIS,
21   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22   * See the License for the specific language governing permissions and
23   * limitations under the License.
24   */
25  
26  /**
27   * Maps data to objects using the column names in the first row of the CSV file
28   * as reference. This way the column order does not matter.
29   *
30   * @param <T> Type of the bean to be returned
31   */
32  public class HeaderColumnNameMappingStrategy<T> extends HeaderNameBaseMappingStrategy<T> {
33  
34      /**
35       * Default constructor. Considered stable.
36       * @see HeaderColumnNameMappingStrategyBuilder
37       */
38      public HeaderColumnNameMappingStrategy() {
39      }
40  
41      /**
42       * Constructor to allow setting options for header name mapping.
43       * Not considered stable. As new options are introduced for the mapping
44       * strategy, they will be introduced here. You are encouraged to use
45       * {@link HeaderColumnNameMappingStrategyBuilder}.
46       *
47       * @param forceCorrectRecordLength If set, every record will be shortened
48       *                                 or lengthened to match the number of
49       *                                 headers
50       * @see HeaderColumnNameMappingStrategyBuilder
51       */
52      public HeaderColumnNameMappingStrategy(boolean forceCorrectRecordLength) {
53          super(forceCorrectRecordLength);
54      }
55  
56      /**
57       * Register a binding between a bean field and a custom converter.
58       *
59       * @param annotation The annotation attached to the bean field
60       * @param localType The class/type in which the field resides
61       * @param localField The bean field
62       */
63      private void registerCustomBinding(CsvCustomBindByName annotation, Class<?> localType, Field localField) {
64          String columnName = annotation.column().toUpperCase().trim();
65          if(StringUtils.isEmpty(columnName)) {
66              columnName = localField.getName().toUpperCase();
67          }
68          @SuppressWarnings("unchecked")
69          Class<? extends AbstractBeanField<T, String>> converter = (Class<? extends AbstractBeanField<T, String>>)annotation
70                  .converter();
71          BeanField<T, String> bean = instantiateCustomConverter(converter);
72          bean.setType(localType);
73          bean.setField(localField);
74          bean.setRequired(annotation.required());
75          fieldMap.put(columnName, bean);
76      }
77  
78      /**
79       * Register a binding between a bean field and a collection converter that
80       * splits input into multiple values.
81       *
82       * @param annotation The annotation attached to the bean field
83       * @param localType The class/type in which the field resides
84       * @param localField The bean field
85       */
86      private void registerSplitBinding(CsvBindAndSplitByName annotation, Class<?> localType, Field localField) {
87          String columnName = annotation.column().toUpperCase().trim();
88          String locale = annotation.locale();
89          String writeLocale = annotation.writeLocaleEqualsReadLocale()
90                  ? locale : annotation.writeLocale();
91          Class<?> elementType = annotation.elementType();
92  
93          CsvConverter converter = determineConverter(
94                  localField, elementType, locale,
95                  writeLocale, annotation.converter());
96          if (StringUtils.isEmpty(columnName)) {
97              fieldMap.put(localField.getName().toUpperCase(),
98                      new BeanFieldSplit<>(
99                              localType, localField, annotation.required(),
100                             errorLocale, converter, annotation.splitOn(),
101                             annotation.writeDelimiter(),
102                             annotation.collectionType(), elementType,
103                             annotation.capture(), annotation.format()));
104         } else {
105             fieldMap.put(columnName, new BeanFieldSplit<>(
106                     localType, localField, annotation.required(),
107                     errorLocale, converter, annotation.splitOn(),
108                     annotation.writeDelimiter(), annotation.collectionType(),
109                     elementType, annotation.capture(), annotation.format()));
110         }
111     }
112 
113     /**
114      * Register a binding between a bean field and a multi-valued converter
115      * that joins values from multiple columns.
116      *
117      * @param annotation The annotation attached to the bean field
118      * @param localType The class/type in which the field resides
119      * @param localField The bean field
120      */
121     private void registerJoinBinding(CsvBindAndJoinByName annotation, Class<?> localType, Field localField) {
122         String columnRegex = annotation.column();
123         String locale = annotation.locale();
124         String writeLocale = annotation.writeLocaleEqualsReadLocale()
125                 ? locale : annotation.writeLocale();
126 
127         CsvConverter converter = determineConverter(
128                 localField, annotation.elementType(), locale,
129                 writeLocale, annotation.converter());
130         if (StringUtils.isEmpty(columnRegex)) {
131             fieldMap.putComplex(localField.getName(),
132                     new BeanFieldJoinStringIndex<>(
133                             localType, localField, annotation.required(),
134                             errorLocale, converter, annotation.mapType(),
135                             annotation.capture(), annotation.format()));
136         } else {
137             fieldMap.putComplex(columnRegex, new BeanFieldJoinStringIndex<>(
138                     localType, localField, annotation.required(), errorLocale,
139                     converter, annotation.mapType(), annotation.capture(),
140                     annotation.format()));
141         }
142     }
143 
144     /**
145      * Register a binding between a bean field and a simple converter.
146      *
147      * @param annotation The annotation attached to the bean field
148      * @param localType The class/type in which the field resides
149      * @param localField The bean field
150      */
151     private void registerBinding(CsvBindByName annotation, Class<?> localType, Field localField) {
152         String columnName = annotation.column().toUpperCase().trim();
153         String locale = annotation.locale();
154         String writeLocale = annotation.writeLocaleEqualsReadLocale()
155                 ? locale : annotation.writeLocale();
156         CsvConverter converter = determineConverter(
157                 localField,
158                 localField.getType(), locale,
159                 writeLocale, null);
160 
161         if (StringUtils.isEmpty(columnName)) {
162             fieldMap.put(localField.getName().toUpperCase(),
163                     new BeanFieldSingleValue<>(
164                             localType, localField, annotation.required(),
165                             errorLocale, converter, annotation.capture(),
166                             annotation.format()));
167         } else {
168             fieldMap.put(columnName, new BeanFieldSingleValue<>(
169                     localType, localField, annotation.required(),
170                     errorLocale, converter, annotation.capture(),
171                     annotation.format()));
172         }
173     }
174 
175     /**
176      * Creates a map of annotated fields in the bean to be processed.
177      * <p>This method is called by {@link #loadFieldMap()} when at least one
178      * relevant annotation is found on a member variable.</p>
179      */
180     @Override
181     protected void loadAnnotatedFieldMap(ListValuedMap<Class<?>, Field> fields) {
182         for (Map.Entry<Class<?>, Field> classField : fields.entries()) {
183             Class<?> localType = classField.getKey();
184             Field localField = classField.getValue();
185 
186             // Always check for a custom converter first.
187             if (localField.isAnnotationPresent(CsvCustomBindByName.class)
188                     || localField.isAnnotationPresent(CsvCustomBindByNames.class)) {
189                 CsvCustomBindByName annotation = selectAnnotationForProfile(
190                         localField.getAnnotationsByType(CsvCustomBindByName.class),
191                         CsvCustomBindByName::profiles);
192                 if(annotation != null) {
193                     registerCustomBinding(annotation, localType, localField);
194                 }
195             }
196 
197             // Then check for a collection
198             else if(localField.isAnnotationPresent(CsvBindAndSplitByName.class)
199                     || localField.isAnnotationPresent(CsvBindAndSplitByNames.class)) {
200                 CsvBindAndSplitByName annotation = selectAnnotationForProfile(
201                         localField.getAnnotationsByType(CsvBindAndSplitByName.class),
202                         CsvBindAndSplitByName::profiles);
203                 if (annotation != null) {
204                     registerSplitBinding(annotation, localType, localField);
205                 }
206             }
207 
208             // Then for a multi-column annotation
209             else if(localField.isAnnotationPresent(CsvBindAndJoinByName.class)
210                     || localField.isAnnotationPresent(CsvBindAndJoinByNames.class)) {
211                 CsvBindAndJoinByName annotation = selectAnnotationForProfile(
212                         localField.getAnnotationsByType(CsvBindAndJoinByName.class),
213                         CsvBindAndJoinByName::profiles);
214                 if (annotation != null) {
215                     registerJoinBinding(annotation, localType, localField);
216                 }
217             }
218 
219             // Otherwise it must be CsvBindByName.
220             else {
221                 CsvBindByName annotation = selectAnnotationForProfile(
222                         localField.getAnnotationsByType(CsvBindByName.class),
223                         CsvBindByName::profiles);
224                 if (annotation != null) {
225                     registerBinding(annotation, localType, localField);
226                 }
227             }
228         }
229     }
230 
231     /**
232      * Returns a set of the annotations that are used for binding in this
233      * mapping strategy.
234      * <p>In this mapping strategy, those are currently:<ul>
235      *     <li>{@link CsvBindByName}</li>
236      *     <li>{@link CsvCustomBindByName}</li>
237      *     <li>{@link CsvBindAndJoinByName}</li>
238      *     <li>{@link CsvBindAndSplitByName}</li>
239      * </ul></p>
240      */
241     @Override
242     protected Set<Class<? extends Annotation>> getBindingAnnotations() {
243         // With Java 9 this can be done more easily with Set.of()
244         return new HashSet<>(Arrays.asList(
245                 CsvBindByNames.class,
246                 CsvCustomBindByNames.class,
247                 CsvBindAndSplitByNames.class,
248                 CsvBindAndJoinByNames.class,
249                 CsvBindByName.class,
250                 CsvCustomBindByName.class,
251                 CsvBindAndSplitByName.class,
252                 CsvBindAndJoinByName.class));
253     }
254 }