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