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 }