1 /*
2 * Copyright 2016 Andrew Rucker Jones.
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 * http://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 com.opencsv.bean;
17
18 import com.opencsv.*;
19 import com.opencsv.bean.exceptionhandler.CsvExceptionHandler;
20 import com.opencsv.bean.exceptionhandler.ExceptionHandlerThrow;
21 import com.opencsv.bean.util.OpencsvUtils;
22 import com.opencsv.enums.CSVReaderNullFieldIndicator;
23 import org.apache.commons.collections4.ListValuedMap;
24 import org.apache.commons.collections4.MultiValuedMap;
25 import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
26 import org.apache.commons.lang3.ObjectUtils;
27 import org.apache.commons.lang3.StringUtils;
28
29 import java.io.Reader;
30 import java.lang.reflect.Field;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.ResourceBundle;
35
36 /**
37 * This class makes it possible to bypass all the intermediate steps and classes
38 * in setting up to read from a CSV source to a list of beans.
39 * <p>This is the place to start if you're reading a CSV source into beans,
40 * especially if you're binding the input's columns to the bean's variables
41 * using the annotations {@link CsvBindByName}, {@link CsvCustomBindByName},
42 * {@link CsvBindByPosition}, or {@link CsvCustomBindByPosition}.</p>
43 * <p>If you want nothing but defaults for the entire import, your code can look
44 * as simple as this, where {@code myreader} is any valid {@link java.io.Reader Reader}:<br>
45 * {@code List<MyBean> result = new CsvToBeanBuilder(myreader).withType(MyBean.class).build().parse();}</p>
46 * <p>This builder is intelligent enough to guess the mapping strategy according to the
47 * following strategy:</p><ol>
48 * <li>If a mapping strategy is explicitly set, it is always used.</li>
49 * <li>If {@link CsvBindByPosition} or {@link CsvCustomBindByPosition} is present,
50 * {@link ColumnPositionMappingStrategy} is used.</li>
51 * <li>Otherwise, {@link HeaderColumnNameMappingStrategy} is used. This includes
52 * the case when {@link CsvBindByName} or {@link CsvCustomBindByName} are being
53 * used. The annotations will automatically be recognized.</li></ol>
54 *
55 * @param <T> Type of the bean to be populated
56 * @author Andrew Rucker Jones
57 * @since 3.9
58 */
59 public class CsvToBeanBuilder<T> {
60
61 /**
62 * @see com.opencsv.bean.CsvToBean#mappingStrategy
63 */
64 private MappingStrategy<? extends T> mappingStrategy = null;
65
66 /**
67 * A CSVReader will be built out of this {@link java.io.Reader}.
68 * @see com.opencsv.bean.CsvToBean#csvReader
69 */
70 private final Reader reader;
71
72 /**
73 * Allow the user to pass in a prebuilt/custom {@link com.opencsv.CSVReader}.
74 */
75 private final CSVReader csvReader;
76
77 /** @see com.opencsv.bean.CsvToBean#filter */
78 private CsvToBeanFilter filter = null;
79
80 /**
81 * @see com.opencsv.bean.CsvToBean#setThrowExceptions(boolean)
82 */
83 private CsvExceptionHandler exceptionHandler = null;
84
85 /** @see com.opencsv.CSVParser#nullFieldIndicator */
86 private CSVReaderNullFieldIndicator nullFieldIndicator = null;
87
88 /** @see com.opencsv.CSVReader#keepCR */
89 private boolean keepCR;
90
91 /** @see com.opencsv.CSVReader#skipLines */
92 private Integer skipLines = null;
93
94 /** @see com.opencsv.CSVReader#verifyReader */
95 private Boolean verifyReader = null;
96
97 /** @see com.opencsv.CSVParser#separator */
98 private Character separator = null;
99
100 /** @see com.opencsv.CSVParser#quotechar */
101 private Character quoteChar = null;
102
103 /** @see com.opencsv.CSVParser#escape */
104 private Character escapeChar = null;
105
106 /**
107 * @see com.opencsv.CSVParser#strictQuotes
108 */
109 private Boolean strictQuotes = null;
110
111 /**
112 * @see com.opencsv.CSVParser#ignoreLeadingWhiteSpace
113 */
114 private Boolean ignoreLeadingWhiteSpace = null;
115
116 /**
117 * @see com.opencsv.CSVParser#ignoreQuotations
118 */
119 private Boolean ignoreQuotations = null;
120
121 /**
122 * @see com.opencsv.bean.CsvToBean#setThrowExceptions(boolean)
123 */
124 private Boolean throwsExceptions = true;
125
126 /**
127 * @see HeaderColumnNameMappingStrategy#type
128 */
129 private Class<? extends T> type = null;
130
131 /**
132 * @see com.opencsv.CSVReader#multilineLimit
133 */
134 private Integer multilineLimit = null;
135
136 /**
137 * @see com.opencsv.bean.CsvToBean#orderedResults
138 */
139 private boolean orderedResults = true;
140
141 /**
142 * @see com.opencsv.bean.CsvToBean#ignoreEmptyLines
143 */
144 private boolean ignoreEmptyLines = false;
145
146 /**
147 * @see com.opencsv.bean.CsvToBean#errorLocale
148 */
149 private Locale errorLocale = Locale.getDefault();
150
151 /**
152 * @see com.opencsv.bean.CsvToBean#verifiers
153 */
154 private final List<BeanVerifier<T>> verifiers = new LinkedList<>();
155
156 /**
157 * @see com.opencsv.bean.AbstractMappingStrategy#ignoredFields
158 */
159 private final ListValuedMap<Class<?>, Field> ignoredFields = new ArrayListValuedHashMap<>();
160
161 /** @see com.opencsv.bean.AbstractMappingStrategy#profile */
162 private String profile = StringUtils.EMPTY;
163
164 /**
165 * Constructor with the one parameter that is most definitely mandatory, and
166 * always will be.
167 * @param reader The reader that is the source of data for the CSV import
168 */
169 public CsvToBeanBuilder(Reader reader) {
170 if(reader == null) {
171 throw new IllegalArgumentException(ResourceBundle
172 .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME) // Must be default locale, because we don't have anything else yet
173 .getString("reader.null"));
174 }
175 this.reader = reader;
176 this.csvReader = null;
177 }
178
179 /**
180 * Constructor with the one parameter that is most definitely mandatory, and
181 * always will be.
182 *
183 * @param csvReader The CSVReader that is the source of data for the CSV import
184 */
185 public CsvToBeanBuilder(CSVReader csvReader) {
186 if (csvReader == null) {
187 throw new IllegalArgumentException(ResourceBundle
188 .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME) // Must be default locale, because we don't have anything else yet
189 .getString("reader.null"));
190 }
191 this.reader = null;
192 this.csvReader = csvReader;
193 }
194
195 /**
196 * Builds the {@link CsvToBean} out of the provided information.
197 * @return A valid {@link CsvToBean}
198 * @throws IllegalStateException If a necessary parameter was not specified.
199 * Currently this means that both the mapping strategy and the bean type
200 * are not set, so it is impossible to determine a mapping strategy.
201 */
202 public CsvToBean<T> build() throws IllegalStateException {
203 // Check for errors in the configuration first
204 if(mappingStrategy == null && type == null) {
205 throw new IllegalStateException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("strategy.type.missing"));
206 }
207
208 // Build Parser and Reader
209 CsvToBean<T> bean = new CsvToBean<>();
210
211 if (csvReader != null) {
212 bean.setCsvReader(csvReader);
213 } else {
214 CSVParser parser = buildParser();
215 bean.setCsvReader(buildReader(parser));
216 }
217
218 // Set variables in CsvToBean itself
219
220 if (exceptionHandler != null) {
221 bean.setExceptionHandler(exceptionHandler);
222 } else {
223 bean.setThrowExceptions(throwsExceptions);
224 }
225
226 bean.setOrderedResults(orderedResults);
227 if (filter != null) {
228 bean.setFilter(filter);
229 }
230 bean.setVerifiers(verifiers);
231
232 // Now find the mapping strategy and ignore irrelevant fields.
233 // It's possible the mapping strategy has already been primed, so only
234 // pass on our data if the user actually gave us something.
235 if(mappingStrategy == null) {
236 mappingStrategy = OpencsvUtils.determineMappingStrategy(type, errorLocale, profile);
237 }
238 if(!ignoredFields.isEmpty()) {
239 mappingStrategy.ignoreFields(ignoredFields);
240 }
241 bean.setMappingStrategy(mappingStrategy);
242
243 // The error locale comes at the end so it can be propagated through all
244 // of the components of CsvToBean, rendering the error locale homogeneous.
245 bean.setErrorLocale(errorLocale);
246 bean.setIgnoreEmptyLines(ignoreEmptyLines);
247
248 return bean;
249 }
250
251 /**
252 * Builds a {@link CSVParser} from the information provided to this builder.
253 * This is an intermediate step in building the {@link CsvToBean}.
254 * @return An appropriate {@link CSVParser}
255 */
256 private CSVParser buildParser() {
257 CSVParserBuilder csvpb = new CSVParserBuilder();
258 if(nullFieldIndicator != null) {
259 csvpb.withFieldAsNull(nullFieldIndicator);
260 }
261 if(separator != null) {
262 csvpb.withSeparator(separator);
263 }
264 if(quoteChar != null) {
265 csvpb.withQuoteChar(quoteChar);
266 }
267 if(escapeChar != null) {
268 csvpb.withEscapeChar(escapeChar);
269 }
270 if(strictQuotes != null) {
271 csvpb.withStrictQuotes(strictQuotes);
272 }
273 if(ignoreLeadingWhiteSpace != null) {
274 csvpb.withIgnoreLeadingWhiteSpace(ignoreLeadingWhiteSpace);
275 }
276 if(ignoreQuotations != null) {
277 csvpb.withIgnoreQuotations(ignoreQuotations);
278 }
279 csvpb.withErrorLocale(errorLocale);
280
281 return csvpb.build();
282 }
283
284 /**
285 * Builds a {@link CSVReader} from the information provided to this builder.
286 * This is an intermediate step in building the {@link CsvToBean}.
287 * @param parser The {@link CSVParser} necessary for this reader
288 * @return An appropriate {@link CSVReader}
289 */
290 private CSVReader buildReader(CSVParser parser) {
291 CSVReaderBuilder csvrb = new CSVReaderBuilder(reader);
292 csvrb.withCSVParser(parser);
293 csvrb.withKeepCarriageReturn(keepCR);
294 if(verifyReader != null) {
295 csvrb.withVerifyReader(verifyReader);
296 }
297 if(skipLines != null) {
298 csvrb.withSkipLines(skipLines);
299 }
300 if(multilineLimit != null) {
301 csvrb.withMultilineLimit(multilineLimit);
302 }
303 csvrb.withErrorLocale(errorLocale);
304 return csvrb.build();
305 }
306
307 /**
308 * @see CsvToBean#setMappingStrategy(com.opencsv.bean.MappingStrategy)
309 * @param mappingStrategy Please see the "See Also" section
310 * @return {@code this}
311 */
312 public CsvToBeanBuilder<T> withMappingStrategy(MappingStrategy<? extends T> mappingStrategy) {
313 this.mappingStrategy = mappingStrategy;
314 return this;
315 }
316
317 /**
318 * @see CsvToBean#setFilter(com.opencsv.bean.CsvToBeanFilter)
319 * @param filter Please see the "See Also" section
320 * @return {@code this}
321 */
322 public CsvToBeanBuilder<T> withFilter(CsvToBeanFilter filter) {
323 this.filter = filter;
324 return this;
325 }
326
327 /**
328 * Sets how the CsvToBean will act when an exception occurs. If both withThrowsExcpetion and
329 * {@link #withExceptionHandler(CsvExceptionHandler)} are used then the withExceptionHandler takes
330 * precedence and is used.
331 *
332 * @see CsvToBean#setThrowExceptions(boolean)
333 * @see #withExceptionHandler(CsvExceptionHandler)
334 * @param throwExceptions Please see the "See Also" section
335 * @return {@code this}
336 */
337 public CsvToBeanBuilder<T> withThrowExceptions(boolean throwExceptions) {
338 this.throwsExceptions = throwExceptions;
339 return this;
340 }
341
342 /**
343 * Sets the handler for recoverable exceptions raised during processing of
344 * records. If both {@link #withThrowExceptions(boolean)} and withExceptionHandler are used then the
345 * withExceptionHandler takes precedence and is used.
346 * <p>If neither this method nor {@link #withThrowExceptions(boolean)} is
347 * called, the default exception handler is
348 * {@link ExceptionHandlerThrow}.</p>
349 * <p>Please note that if both this method and
350 * {@link #withThrowExceptions(boolean)} are called, the last call wins.</p>
351 *
352 * @param exceptionHandler The exception handler to be used. If {@code null},
353 * this method does nothing.
354 * @return {@code this}
355 * @since 5.2
356 */
357 public CsvToBeanBuilder<T> withExceptionHandler(CsvExceptionHandler exceptionHandler) {
358 if(exceptionHandler != null) {
359 this.exceptionHandler = exceptionHandler;
360 }
361 return this;
362 }
363
364 /**
365 * @param indicator Which field content will be returned as null: EMPTY_SEPARATORS, EMPTY_QUOTES,
366 * BOTH, NEITHER (default)
367 * @return {@code this}
368 */
369 public CsvToBeanBuilder<T> withFieldAsNull(CSVReaderNullFieldIndicator indicator) {
370 this.nullFieldIndicator = indicator;
371 return this;
372 }
373
374 /**
375 * @param keepCR True to keep carriage returns in data read, false otherwise
376 * @return {@code this}
377 */
378 public CsvToBeanBuilder<T> withKeepCarriageReturn(boolean keepCR) {
379 this.keepCR = keepCR;
380 return this;
381 }
382
383 /**
384 * @see CSVReaderBuilder#withVerifyReader(boolean)
385 * @param verifyReader Please see the "See Also" section
386 * @return {@code this}
387 */
388 public CsvToBeanBuilder<T> withVerifyReader(boolean verifyReader) {
389 this.verifyReader = verifyReader;
390 return this;
391 }
392
393 /**
394 * @see CSVReaderBuilder#withSkipLines(int)
395 * @param skipLines Please see the "See Also" section
396 * @return {@code this}
397 */
398 public CsvToBeanBuilder<T> withSkipLines(
399 final int skipLines) {
400 this.skipLines = skipLines;
401 return this;
402 }
403
404 /**
405 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale)
406 * @param separator Please see the "See Also" section
407 * @return {@code this}
408 */
409 public CsvToBeanBuilder<T> withSeparator(char separator) {
410 this.separator = separator;
411 return this;
412 }
413
414 /**
415 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale)
416 * @param quoteChar Please see the "See Also" section
417 * @return {@code this}
418 */
419 public CsvToBeanBuilder<T> withQuoteChar(char quoteChar) {
420 this.quoteChar = quoteChar;
421 return this;
422 }
423
424 /**
425 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale)
426 * @param escapeChar Please see the "See Also" section
427 * @return {@code this}
428 */
429 public CsvToBeanBuilder<T> withEscapeChar(char escapeChar) {
430 this.escapeChar = escapeChar;
431 return this;
432 }
433
434 /**
435 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale)
436 * @param strictQuotes Please see the "See Also" section
437 * @return {@code this}
438 */
439 public CsvToBeanBuilder<T> withStrictQuotes(boolean strictQuotes) {
440 this.strictQuotes = strictQuotes;
441 return this;
442 }
443
444 /**
445 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale)
446 * @param ignoreLeadingWhiteSpace Please see the "See Also" section
447 * @return {@code this}
448 */
449 public CsvToBeanBuilder<T> withIgnoreLeadingWhiteSpace(boolean ignoreLeadingWhiteSpace) {
450 this.ignoreLeadingWhiteSpace = ignoreLeadingWhiteSpace;
451 return this;
452 }
453
454 /**
455 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale)
456 * @param ignoreQuotations Please see the "See Also" section
457 * @return {@code this}
458 */
459 public CsvToBeanBuilder<T> withIgnoreQuotations(boolean ignoreQuotations) {
460 this.ignoreQuotations = ignoreQuotations;
461 return this;
462 }
463
464 /**
465 * Sets the type of the bean to be populated.
466 * Ignored if {@link #withMappingStrategy(com.opencsv.bean.MappingStrategy)}
467 * is called.
468 * @param type Class of the destination bean
469 * @return {@code this}
470 * @see HeaderColumnNameMappingStrategy#setType(java.lang.Class)
471 * @see ColumnPositionMappingStrategy#setType(java.lang.Class)
472 */
473 public CsvToBeanBuilder<T> withType(Class<? extends T> type) {
474 this.type = type;
475 return this;
476 }
477
478 /**
479 * Sets the maximum number of lines allowed in a multiline record.
480 * More than this number in one record results in an IOException.
481 *
482 * @param multilineLimit No more than this number of lines is allowed in a
483 * single input record. The default is {@link CSVReader#DEFAULT_MULTILINE_LIMIT}.
484 * @return {@code this}
485 */
486 public CsvToBeanBuilder<T> withMultilineLimit(int multilineLimit) {
487 this.multilineLimit = multilineLimit;
488 return this;
489 }
490
491 /**
492 * Sets whether the resulting beans must be ordered as in the input.
493 *
494 * @param orderedResults Whether to order the results or not
495 * @return {@code this}
496 * @see CsvToBean#setOrderedResults(boolean)
497 * @since 4.0
498 */
499 public CsvToBeanBuilder<T> withOrderedResults(boolean orderedResults) {
500 this.orderedResults = orderedResults;
501 return this;
502 }
503
504 /**
505 * Sets the locale for all error messages.
506 *
507 * @param errorLocale Locale for error messages
508 * @return {@code this}
509 * @see CsvToBean#setErrorLocale(java.util.Locale)
510 * @since 4.0
511 */
512 public CsvToBeanBuilder<T> withErrorLocale(Locale errorLocale) {
513 this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
514 return this;
515 }
516
517 /**
518 * Adds a {@link BeanVerifier} to the list of verifiers to run on all
519 * beans created.
520 * This method may be called as many times as desired. All added verifiers
521 * will be run on every bean. No guarantee is made as to the order in which
522 * the verifiers are run.
523 *
524 * @param verifier A new verifier that is to process all beans after
525 * creation. {@code null} is permissible but has no effect.
526 * @return {@code this}
527 * @since 4.4
528 */
529 public CsvToBeanBuilder<T> withVerifier(BeanVerifier<T> verifier) {
530 if(verifier != null) {
531 verifiers.add(verifier);
532 }
533 return this;
534 }
535
536 /**
537 * Adds a {@link Field} to the list of fields opencsv should ignore
538 * completely.
539 * <p>May be called as many times as necessary.</p>
540 * @param type The class opencsv will encounter the field in during
541 * processing. In the case of inheritance, this may not be the
542 * declaring class.
543 * @param field The field opencsv is to ignore
544 * @return {@code this}
545 * @throws IllegalArgumentException If one of the parameters is
546 * {@code null} or {@code field} cannot be found in {@code type}.
547 * @since 5.0
548 * @see MappingStrategy#ignoreFields(MultiValuedMap)
549 */
550 public CsvToBeanBuilder<T> withIgnoreField(Class<?> type, Field field) throws IllegalArgumentException {
551 if (type != null && field != null && field.getDeclaringClass().isAssignableFrom(type)) {
552 ignoredFields.put(type, field);
553 } else {
554 throw new IllegalArgumentException(ResourceBundle.getBundle(
555 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
556 .getString("ignore.field.inconsistent"));
557 }
558 return this;
559 }
560
561 /**
562 * @param ignore Please see the "See Also" section
563 * @return {@code this}
564 * @see CsvToBean#ignoreEmptyLines
565 */
566 public CsvToBeanBuilder<T> withIgnoreEmptyLine(boolean ignore) {
567 this.ignoreEmptyLines = ignore;
568 return this;
569 }
570
571 /**
572 * Selects a profile for deciding which configurations to use for the bean
573 * fields.
574 *
575 * @param profile The name of the profile to be used
576 * @return {@code this}
577 * @since 5.4
578 */
579 public CsvToBeanBuilder<T> withProfile(String profile) {
580 this.profile = profile;
581 return this;
582 }
583 }