View Javadoc
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 }