View Javadoc
1   package com.opencsv.bean;
2   
3   /*
4    Copyright 2007 Kyle Miller.
5   
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9   
10   http://www.apache.org/licenses/LICENSE-2.0
11  
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17   */
18  
19  import com.opencsv.CSVReader;
20  import com.opencsv.ICSVParser;
21  import com.opencsv.bean.concurrent.CompleteFileReader;
22  import com.opencsv.bean.concurrent.LineExecutor;
23  import com.opencsv.bean.concurrent.ProcessCsvLine;
24  import com.opencsv.bean.concurrent.SingleLineReader;
25  import com.opencsv.bean.exceptionhandler.CsvExceptionHandler;
26  import com.opencsv.bean.exceptionhandler.ExceptionHandlerQueue;
27  import com.opencsv.bean.exceptionhandler.ExceptionHandlerThrow;
28  import com.opencsv.bean.util.OrderedObject;
29  import com.opencsv.exceptions.CsvException;
30  import com.opencsv.exceptions.CsvValidationException;
31  import org.apache.commons.lang3.ObjectUtils;
32  
33  import java.io.IOException;
34  import java.util.*;
35  import java.util.concurrent.ArrayBlockingQueue;
36  import java.util.concurrent.BlockingQueue;
37  import java.util.concurrent.LinkedBlockingQueue;
38  import java.util.stream.Collectors;
39  import java.util.stream.Stream;
40  import java.util.stream.StreamSupport;
41  
42  /**
43   * Converts CSV data to objects.
44   * Mixing the {@link #parse()} method with the {@link #iterator() Iterator} is
45   * not supported and will lead to unpredictable results. Additionally, reusing
46   * an instance of this class after all beans have been read is not supported
47   * and will certainly break something.
48   *
49   * @param <T> Class to convert the objects to.
50   */
51  public class CsvToBean<T> implements Iterable<T> {
52  
53      /**
54       * A list of all exceptions during parsing and mapping of the input.
55       */
56      private final List<CsvException> capturedExceptions = new LinkedList<>();
57  
58      /**
59       * The mapping strategy to be used by this CsvToBean.
60       */
61      private MappingStrategy<? extends T> mappingStrategy;
62  
63      /**
64       * The reader this class will use to access the data to be read.
65       */
66      private CSVReader csvReader;
67  
68      /**
69       * The filter this class will use on the beans it reads.
70       */
71      private CsvToBeanFilter filter = null;
72  
73      /**
74       * Determines how exceptions thrown during processing will be handled.
75       */
76      private CsvExceptionHandler exceptionHandler = new ExceptionHandlerThrow();
77  
78      /**
79       * Determines whether resulting data sets have to be in the same order as
80       * the input.
81       */
82      private boolean orderedResults = true;
83  
84      /**
85       * The {@link java.util.concurrent.ExecutorService} for parallel processing
86       * of beans.
87       */
88      private LineExecutor<T> executor;
89  
90      /**
91       * The errorLocale for error messages.
92       */
93      private Locale errorLocale = Locale.getDefault();
94  
95      /**
96       * All verifiers that should be run on beans after creation but before
97       * returning them to the caller.
98       */
99      private List<BeanVerifier<T>> verifiers = Collections.<BeanVerifier<T>>emptyList();
100 
101     /**
102      * When an empty line is encountered (not part of the data) then it is
103      * ignored.
104      * By default this is {@code false}, which means an exception is thrown if
105      * there are required fields or the number of fields do not match the
106      * number of headers.
107      */
108     private boolean ignoreEmptyLines = false;
109 
110     /**
111      * Default constructor.
112      */
113     public CsvToBean() {
114     }
115 
116     /**
117      * Parses the input based on parameters already set through other methods.
118      *
119      * @return A list of populated beans based on the input
120      * @throws IllegalStateException If either MappingStrategy or CSVReader is
121      *                               not specified
122      * @see #stream()
123      * @see #iterator()
124      */
125     public List<T> parse() throws IllegalStateException {
126         return stream().collect(Collectors.toList());
127     }
128 
129     /**
130      * Parses the input based on parameters already set through other methods.
131      * This method saves a marginal amount of time and storage compared to
132      * {@link #parse()} because it avoids the intermediate storage of the
133      * results in a {@link java.util.List}. If you plan on further processing
134      * the results as a {@link java.util.stream.Stream}, use this method.
135      *
136      * @return A stream of populated beans based on the input
137      * @throws IllegalStateException If either MappingStrategy or CSVReader is
138      *                               not specified
139      * @see #parse()
140      * @see #iterator()
141      */
142     public Stream<T> stream() throws IllegalStateException {
143         prepareToReadInput();
144         CompleteFileReader<T> completeFileReader = new CompleteFileReader<>(
145                 csvReader, filter, ignoreEmptyLines,
146                 mappingStrategy, exceptionHandler, verifiers);
147         executor = new LineExecutor<T>(orderedResults, errorLocale, completeFileReader);
148         executor.prepare();
149         return StreamSupport.stream(executor, false);
150     }
151 
152     /**
153      * Returns the list of all exceptions that would have been thrown during the
154      * import, but were queued by the exception handler.
155      * <p>The results returned by this method are not consistent until parsing
156      * is concluded.</p>
157      *
158      * @return The list of exceptions captured while processing the input file
159      * @see #setExceptionHandler(CsvExceptionHandler)
160      * @see #setThrowExceptions(boolean)
161      */
162     public List<CsvException> getCapturedExceptions() {
163         // The exceptions are stored in different places, dependent on
164         // whether or not the iterator is used.
165         return executor != null ? executor.getCapturedExceptions() : capturedExceptions;
166     }
167 
168     /**
169      * Sets the mapping strategy to be used by this bean.
170      *
171      * @param mappingStrategy Mapping strategy to convert CSV input to a bean
172      */
173     public void setMappingStrategy(MappingStrategy<? extends T> mappingStrategy) {
174         this.mappingStrategy = mappingStrategy;
175     }
176 
177     /**
178      * Sets the reader to be used to read in the information from the CSV input.
179      *
180      * @param csvReader Reader for input
181      */
182     public void setCsvReader(CSVReader csvReader) {
183         this.csvReader = csvReader;
184     }
185 
186     /**
187      * Sets a filter to selectively remove some lines of input before they
188      * become beans.
189      *
190      * @param filter A class that filters the input lines
191      */
192     public void setFilter(CsvToBeanFilter filter) {
193         this.filter = filter;
194     }
195 
196     /**
197      * Determines whether errors during import should be thrown or kept in a
198      * list for later retrieval via {@link #getCapturedExceptions()}.
199      * <p>This is a convenience function and is maintained for backwards
200      * compatibility. Passing in {@code true} is equivalent to
201      * {@code setExceptionHandler(new ExceptionHandlerThrow())}
202      * and {@code false} is equivalent to
203      * {@code setExceptionHandler(new ExceptionHandlerQueue())}</p>
204      * <p>Please note that if both this method and
205      * {@link #setExceptionHandler(CsvExceptionHandler)} are called,
206      * the last call wins.</p>
207      *
208      * @param throwExceptions Whether or not to throw exceptions during
209      *                        processing
210      * @see #setExceptionHandler(CsvExceptionHandler)
211      */
212     public void setThrowExceptions(boolean throwExceptions) {
213         if (throwExceptions) {
214             exceptionHandler = new ExceptionHandlerThrow();
215         } else {
216             exceptionHandler = new ExceptionHandlerQueue();
217         }
218     }
219 
220     /**
221      * Sets the handler for recoverable exceptions raised during processing of
222      * records.
223      * <p>If neither this method nor {@link #setThrowExceptions(boolean)} is
224      * called, the default exception handler is
225      * {@link ExceptionHandlerThrow}.</p>
226      * <p>Please note that if both this method and
227      * {@link #setThrowExceptions(boolean)} are called, the last call wins.</p>
228      *
229      * @param handler The exception handler to be used. If {@code null},
230      *                this method does nothing.
231      * @since 5.2
232      */
233     public void setExceptionHandler(CsvExceptionHandler handler) {
234         if (handler != null) {
235             exceptionHandler = handler;
236         }
237     }
238 
239     /**
240      * Package scope method currently used by the CsvToBeanBuilderTest
241      *
242      * @return CsvExceptionHandler used by the CsvToBean object.
243      */
244     CsvExceptionHandler getExceptionHandler() {
245         return exceptionHandler;
246     }
247 
248     /**
249      * Sets whether or not results must be returned in the same order in which
250      * they appear in the input.
251      * The default is that order is preserved. If your data do not need to be
252      * ordered, you can get a slight performance boost by setting
253      * {@code orderedResults} to {@code false}. The lack of ordering then also
254      * applies to any captured exceptions, if you have chosen not to have
255      * exceptions thrown.
256      *
257      * @param orderedResults Whether or not the beans returned are in the same
258      *                       order they appeared in the input
259      * @since 4.0
260      */
261     public void setOrderedResults(boolean orderedResults) {
262         this.orderedResults = orderedResults;
263     }
264 
265     /**
266      * Sets the locale for error messages.
267      *
268      * @param errorLocale Locale for error messages. If null, the default locale
269      *                    is used.
270      * @since 4.0
271      */
272     public void setErrorLocale(Locale errorLocale) {
273         this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
274         if (csvReader != null) {
275             csvReader.setErrorLocale(this.errorLocale);
276         }
277         if (mappingStrategy != null) {
278             mappingStrategy.setErrorLocale(this.errorLocale);
279         }
280     }
281 
282     /**
283      * Sets the list of verifiers to be run on all beans after creation.
284      *
285      * @param verifiers A list of verifiers. May be {@code null}, in which
286      *                  case, no verifiers are run.
287      * @since 4.4
288      */
289     public void setVerifiers(List<BeanVerifier<T>> verifiers) {
290         this.verifiers = ObjectUtils.defaultIfNull(verifiers, Collections.<BeanVerifier<T>>emptyList());
291     }
292 
293     private void prepareToReadInput() throws IllegalStateException {
294         // First verify that the user hasn't failed to give us the information
295         // we need to do his or her work for him or her.
296         if (mappingStrategy == null || csvReader == null) {
297             throw new IllegalStateException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("specify.strategy.reader"));
298         }
299 
300         // Get the header information
301         try {
302             mappingStrategy.captureHeader(csvReader);
303         } catch (Exception e) {
304             throw new RuntimeException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("header.error"), e);
305         }
306     }
307 
308     /**
309      * The iterator returned by this method takes one line of input at a time
310      * and returns one bean at a time.
311      * <p>The advantage to this method is saving memory. The cost is the loss of
312      * parallel processing, reducing throughput.</p>
313      * <p>The iterator respects all aspects of {@link CsvToBean}, including
314      * filters and capturing exceptions.</p>
315      *
316      * @return An iterator over the beans created from the input
317      * @see #parse()
318      * @see #stream()
319      */
320     @Override
321     public Iterator<T> iterator() {
322         prepareToReadInput();
323         return new CsvToBeanIterator();
324     }
325 
326     /**
327      * Ignores any blank lines in the data that are not part of a field.
328      *
329      * @param ignoreEmptyLines {@code true} to ignore empty lines, {@code false} otherwise
330      */
331     public void setIgnoreEmptyLines(boolean ignoreEmptyLines) {
332         this.ignoreEmptyLines = ignoreEmptyLines;
333     }
334 
335     /**
336      * A private inner class for implementing an iterator for the input data.
337      */
338     private class CsvToBeanIterator implements Iterator<T> {
339         private final BlockingQueue<OrderedObject<T>> resultantBeansQueue;
340         private final BlockingQueue<OrderedObject<CsvException>> thrownExceptionsQueue;
341         private final SingleLineReader lineReader = new SingleLineReader(csvReader, ignoreEmptyLines);
342         private String[] line = null;
343         private long lineProcessed = 0;
344         private T bean;
345 
346         CsvToBeanIterator() {
347             resultantBeansQueue = new ArrayBlockingQueue<>(1);
348             thrownExceptionsQueue = new LinkedBlockingQueue<>();
349             readSingleLine();
350         }
351 
352         private void processException() {
353             // At least one exception was thrown
354             OrderedObject<CsvException> o = thrownExceptionsQueue.poll();
355             while (o != null && o.getElement() != null) {
356                 capturedExceptions.add(o.getElement());
357                 o = thrownExceptionsQueue.poll();
358             }
359         }
360 
361         private void readLineWithPossibleError() throws IOException, CsvValidationException {
362             // Read a line
363             bean = null;
364             while (bean == null && null != (line = lineReader.readNextLine())) {
365                 lineProcessed = lineReader.getLinesRead();
366 
367                 // Create a bean
368                 ProcessCsvLine<T> proc = new ProcessCsvLine<>(
369                         lineProcessed, mappingStrategy, filter, verifiers,
370                         line, resultantBeansQueue, thrownExceptionsQueue,
371                         new TreeSet<>(), exceptionHandler);
372                 proc.run();
373 
374                 if (!thrownExceptionsQueue.isEmpty()) {
375                     processException();
376                 } else {
377                     // No exception, so there really must always be a bean
378                     // . . . unless it was filtered
379                     OrderedObject<T> o = resultantBeansQueue.poll();
380                     bean = o == null ? null : o.getElement();
381                 }
382             }
383             if (line == null) {
384                 // There isn't any more
385                 bean = null;
386             }
387         }
388 
389         private void readSingleLine() {
390             try {
391                 readLineWithPossibleError();
392             } catch (IOException | CsvValidationException e) {
393                 line = null;
394                 throw new RuntimeException(String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("parsing.error"),
395                         lineProcessed, Arrays.toString(line)), e);
396             }
397         }
398 
399         @Override
400         public boolean hasNext() {
401             return bean != null;
402         }
403 
404         @Override
405         public T next() {
406             if (bean == null) {
407                 throw new NoSuchElementException();
408             }
409             T intermediateBean = bean;
410             readSingleLine();
411             return intermediateBean;
412         }
413 
414         @Override
415         public void remove() {
416             throw new UnsupportedOperationException(ResourceBundle
417                     .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
418                     .getString("read.only.iterator"));
419         }
420     }
421 }