/*
 * Copyright 2016 Andrew Rucker Jones.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opencsv.bean;

import com.opencsv.CSVWriter;
import com.opencsv.ICSVParser;
import com.opencsv.ICSVWriter;
import com.opencsv.bean.concurrent.BeanExecutor;
import com.opencsv.bean.concurrent.OrderedObject;
import com.opencsv.bean.concurrent.ProcessCsvBean;
import com.opencsv.exceptions.CsvDataTypeMismatchException;
import com.opencsv.exceptions.CsvException;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
import com.opencsv.exceptions.CsvRuntimeException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.iterators.PeekingIterator;
import org.apache.commons.lang3.ObjectUtils;

import java.io.Writer;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Stream;

This class writes beans out in CSV format to a Writer, keeping state information and making an intelligent guess at the mapping strategy to be applied.

This class implements multi-threading on writing more than one bean, so there should be no need to use it across threads in an application. As such, it is not thread-safe.

Author:Andrew Rucker Jones
Type parameters:
  • <T> – Type of the bean to be written
See Also:
Since:3.9
/** * This class writes beans out in CSV format to a {@link java.io.Writer}, * keeping state information and making an intelligent guess at the mapping * strategy to be applied. * <p>This class implements multi-threading on writing more than one bean, so * there should be no need to use it across threads in an application. As such, * it is not thread-safe.</p> * * @param <T> Type of the bean to be written * @author Andrew Rucker Jones * @see OpencsvUtils#determineMappingStrategy(java.lang.Class, java.util.Locale) * @since 3.9 */
public class StatefulBeanToCsv<T> { private static final char NO_CHARACTER = '\0';
The beans being written are counted in the order they are written.
/** * The beans being written are counted in the order they are written. */
private int lineNumber = 0; private final char separator; private final char quotechar; private final char escapechar; private final String lineEnd; private boolean headerWritten = false; private MappingStrategy<T> mappingStrategy; private final Writer writer; private ICSVWriter csvwriter; private boolean throwExceptions; private List<CsvException> capturedExceptions = new ArrayList<>(); private boolean orderedResults = true; private BeanExecutor<T> executor = null; private Locale errorLocale = Locale.getDefault(); private boolean applyQuotesToAll; private final MultiValuedMap<Class<?>, Field> ignoredFields;
Constructor used when supplying a Writer instead of a CsvWriter class. It is defined as package protected to ensure that StatefulBeanToCsvBuilder is always used.
Params:
  • escapechar – The escape character to use when writing a CSV file
  • lineEnd – The line ending to use when writing a CSV file
  • mappingStrategy – The mapping strategy to use when writing a CSV file
  • quotechar – The quote character to use when writing a CSV file
  • separator – The field separator to use when writing a CSV file
  • throwExceptions – Whether or not exceptions should be thrown while writing the CSV file. If not, they are collected and can be retrieved via getCapturedExceptions().
  • writer – A Writer for writing the beans as a CSV to
  • applyQuotesToAll – Whether all output fields should be quoted
  • ignoredFields – The fields to ignore during processing. May be null.
/** * Constructor used when supplying a Writer instead of a CsvWriter class. * It is defined as package protected to ensure that {@link StatefulBeanToCsvBuilder} is always used. * * @param escapechar The escape character to use when writing a CSV file * @param lineEnd The line ending to use when writing a CSV file * @param mappingStrategy The mapping strategy to use when writing a CSV file * @param quotechar The quote character to use when writing a CSV file * @param separator The field separator to use when writing a CSV file * @param throwExceptions Whether or not exceptions should be thrown while * writing the CSV file. If not, they are collected and can be retrieved * via {@link #getCapturedExceptions()}. * @param writer A {@link java.io.Writer} for writing the beans as a CSV to * @param applyQuotesToAll Whether all output fields should be quoted * @param ignoredFields The fields to ignore during processing. May be {@code null}. */
StatefulBeanToCsv(char escapechar, String lineEnd, MappingStrategy<T> mappingStrategy, char quotechar, char separator, boolean throwExceptions, Writer writer, boolean applyQuotesToAll, MultiValuedMap<Class<?>, Field> ignoredFields) { this.escapechar = escapechar; this.lineEnd = lineEnd; this.mappingStrategy = mappingStrategy; this.quotechar = quotechar; this.separator = separator; this.throwExceptions = throwExceptions; this.writer = writer; this.applyQuotesToAll = applyQuotesToAll; this.ignoredFields = ignoredFields; }
Constructor used to allow building of a StatefulBeanToCsv with a user-supplied ICSVWriter class.
Params:
  • mappingStrategy – The mapping strategy to use when writing a CSV file
  • throwExceptions – Whether or not exceptions should be thrown while writing the CSV file. If not, they are collected and can be retrieved via getCapturedExceptions().
  • applyQuotesToAll – Whether all output fields should be quoted
  • csvWriter – An user-supplied ICSVWriter for writing beans to a CSV output
  • ignoredFields – The fields to ignore during processing. May be null.
/** * Constructor used to allow building of a {@link com.opencsv.bean.StatefulBeanToCsv} * with a user-supplied {@link com.opencsv.ICSVWriter} class. * * @param mappingStrategy The mapping strategy to use when writing a CSV file * @param throwExceptions Whether or not exceptions should be thrown while * writing the CSV file. If not, they are collected and can be retrieved * via {@link #getCapturedExceptions()}. * @param applyQuotesToAll Whether all output fields should be quoted * @param csvWriter An user-supplied {@link com.opencsv.ICSVWriter} for writing beans to a CSV output * @param ignoredFields The fields to ignore during processing. May be {@code null}. */
public StatefulBeanToCsv(MappingStrategy<T> mappingStrategy, boolean throwExceptions, boolean applyQuotesToAll, ICSVWriter csvWriter, MultiValuedMap<Class<?>, Field> ignoredFields) { this.mappingStrategy = mappingStrategy; this.throwExceptions = throwExceptions; this.applyQuotesToAll = applyQuotesToAll; this.csvwriter = csvWriter; this.escapechar = NO_CHARACTER; this.lineEnd = ""; this.quotechar = NO_CHARACTER; this.separator = NO_CHARACTER; this.writer = null; this.ignoredFields = ignoredFields; }
Custodial tasks that must be performed before beans are written to a CSV destination for the first time.
Params:
  • bean – Any bean to be written. Used to determine the mapping strategy automatically. The bean itself is not written to the output by this method.
Throws:
  • CsvRequiredFieldEmptyException – If a required header is missing while attempting to write. Since every other header is hard-wired through the bean fields and their associated annotations, this can only happen with multi-valued fields.
/** * Custodial tasks that must be performed before beans are written to a CSV * destination for the first time. * * @param bean Any bean to be written. Used to determine the mapping * strategy automatically. The bean itself is not written to the output by * this method. * @throws CsvRequiredFieldEmptyException If a required header is missing * while attempting to write. Since every other header is hard-wired * through the bean fields and their associated annotations, this can only * happen with multi-valued fields. */
private void beforeFirstWrite(T bean) throws CsvRequiredFieldEmptyException { // Determine mapping strategy if (mappingStrategy == null) { mappingStrategy = OpencsvUtils.determineMappingStrategy((Class<T>) bean.getClass(), errorLocale); } // Ignore fields. It's possible the mapping strategy has already been // primed, so only pass on our data if the user actually gave us // something. if(!ignoredFields.isEmpty()) { mappingStrategy.ignoreFields(ignoredFields); } // Build CSVWriter if (csvwriter == null) { csvwriter = new CSVWriter(writer, separator, quotechar, escapechar, lineEnd); } // Write the header String[] header = mappingStrategy.generateHeader(bean); if (header.length > 0) { csvwriter.writeNext(header, applyQuotesToAll); } headerWritten = true; }
Writes a bean out to the Writer provided to the constructor.
Params:
  • bean – A bean to be written to a CSV destination
Throws:
/** * Writes a bean out to the {@link java.io.Writer} provided to the * constructor. * * @param bean A bean to be written to a CSV destination * @throws CsvDataTypeMismatchException If a field of the bean is * annotated improperly or an unsupported data type is supposed to be * written * @throws CsvRequiredFieldEmptyException If a field is marked as required, * but the source is null */
public void write(T bean) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { // Write header if (bean != null) { if (!headerWritten) { beforeFirstWrite(bean); } // Process the bean BlockingQueue<OrderedObject<String[]>> resultantLineQueue = new ArrayBlockingQueue<>(1); BlockingQueue<OrderedObject<CsvException>> thrownExceptionsQueue = new ArrayBlockingQueue<>(1); ProcessCsvBean<T> proc = new ProcessCsvBean<>(++lineNumber, mappingStrategy, bean, resultantLineQueue, thrownExceptionsQueue, throwExceptions); try { proc.run(); } catch (RuntimeException re) { if (re.getCause() != null) { if (re.getCause() instanceof CsvRuntimeException) { // Can't currently happen, but who knows what might be // in the future? I'm certain we wouldn't want to wrap // these in another RuntimeException. throw (CsvRuntimeException) re.getCause(); } if (re.getCause() instanceof CsvDataTypeMismatchException) { throw (CsvDataTypeMismatchException) re.getCause(); } if (re.getCause() instanceof CsvRequiredFieldEmptyException) { throw (CsvRequiredFieldEmptyException) re.getCause(); } } throw re; } // Write out the result if (!thrownExceptionsQueue.isEmpty()) { OrderedObject<CsvException> o = thrownExceptionsQueue.poll(); if (o != null && o.getElement() != null) { capturedExceptions.add(o.getElement()); } } else { // No exception, so there really must always be a string OrderedObject<String[]> result = resultantLineQueue.poll(); if (result != null && result.getElement() != null) { csvwriter.writeNext(result.getElement(), applyQuotesToAll); } } } } private void submitAllLines(Iterator<T> beans) throws InterruptedException { while (beans.hasNext()) { T bean = beans.next(); if (bean != null) { executor.submitBean(++lineNumber, mappingStrategy, bean, throwExceptions); } } executor.complete(); }
Writes a list of beans out to the Writer provided to the constructor.
Params:
  • beans – A list of beans to be written to a CSV destination
Throws:
/** * Writes a list of beans out to the {@link java.io.Writer} provided to the * constructor. * * @param beans A list of beans to be written to a CSV destination * @throws CsvDataTypeMismatchException If a field of the beans is * annotated improperly or an unsupported data type is supposed to be * written * @throws CsvRequiredFieldEmptyException If a field is marked as required, * but the source is null */
public void write(List<T> beans) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { if (CollectionUtils.isNotEmpty(beans)) { write(beans.iterator()); } }
Writes an iterator of beans out to the Writer provided to the constructor.
Params:
  • iBeans – An iterator of beans to be written to a CSV destination
Throws:
/** * Writes an iterator of beans out to the {@link java.io.Writer} provided to the * constructor. * * @param iBeans An iterator of beans to be written to a CSV destination * @throws CsvDataTypeMismatchException If a field of the beans is annotated improperly or an unsupported * data type is supposed to be written * @throws CsvRequiredFieldEmptyException If a field is marked as required, but the source is null */
public void write(Iterator<T> iBeans) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { PeekingIterator<T> beans = new PeekingIterator<>(iBeans); T firstBean = beans.peek(); if (!beans.hasNext()) { return; } // Write header if (!headerWritten) { beforeFirstWrite(firstBean); } executor = new BeanExecutor<>(orderedResults); executor.prepare(); // Process the beans try { submitAllLines(beans); } catch (RejectedExecutionException e) { // An exception in one of the bean writing threads prompted the // executor service to shutdown before we were done. if (executor.getTerminalException() instanceof RuntimeException) { throw (RuntimeException) executor.getTerminalException(); } if (executor.getTerminalException() instanceof CsvDataTypeMismatchException) { throw (CsvDataTypeMismatchException) executor.getTerminalException(); } if (executor.getTerminalException() instanceof CsvRequiredFieldEmptyException) { throw (CsvRequiredFieldEmptyException) executor .getTerminalException(); } throw new RuntimeException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("error.writing.beans"), executor.getTerminalException()); } catch (Exception e) { // Exception during parsing. Always unrecoverable. // I can't find a way to create this condition in the current // code, but we must have a catch-all clause. executor.shutdownNow(); if (executor.getTerminalException() instanceof RuntimeException) { throw (RuntimeException) executor.getTerminalException(); } throw new RuntimeException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("error.writing.beans"), e); } capturedExceptions.addAll(executor.getCapturedExceptions()); executor.resultStream().forEach(l -> csvwriter.writeNext(l, applyQuotesToAll)); }
Writes a stream of beans out to the Writer provided to the constructor.
Params:
  • beans – A stream of beans to be written to a CSV destination
Throws:
/** * Writes a stream of beans out to the {@link java.io.Writer} provided to the * constructor. * * @param beans A stream of beans to be written to a CSV destination * @throws CsvDataTypeMismatchException If a field of the beans is annotated improperly or an unsupported * data type is supposed to be written * @throws CsvRequiredFieldEmptyException If a field is marked as required, but the source is null */
public void write(Stream<T> beans) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { write(beans.iterator()); }
Sets whether or not results must be written in the same order in which they appear in the list of beans provided as input. The default is that order is preserved. If your data do not need to be ordered, you can get a slight performance boost by setting orderedResults to false. The lack of ordering then also applies to any captured exceptions, if you have chosen not to have exceptions thrown.
Params:
  • orderedResults – Whether or not the lines written are in the same order they appeared in the input
Since:4.0
/** * Sets whether or not results must be written in the same order in which * they appear in the list of beans provided as input. * The default is that order is preserved. If your data do not need to be * ordered, you can get a slight performance boost by setting * {@code orderedResults} to {@code false}. The lack of ordering then also * applies to any captured exceptions, if you have chosen not to have * exceptions thrown. * * @param orderedResults Whether or not the lines written are in the same * order they appeared in the input * @since 4.0 */
public void setOrderedResults(boolean orderedResults) { this.orderedResults = orderedResults; }
Returns:Whether or not exceptions are thrown. If they are not thrown, they are captured and returned later via getCapturedExceptions().
/** * @return Whether or not exceptions are thrown. If they are not thrown, * they are captured and returned later via {@link #getCapturedExceptions()}. */
public boolean isThrowExceptions() { return throwExceptions; }
Any exceptions captured during writing of beans to a CSV destination can be retrieved through this method.

Reads from the list are destructive! Calling this method will clear the list of captured exceptions. However, calling write(List) or write(Object) multiple times with no intervening call to this method will not clear the list of captured exceptions, but rather add to it if further exceptions are thrown.

Returns:A list of exceptions that would have been thrown during any and all read operations since the last call to this method
/** * Any exceptions captured during writing of beans to a CSV destination can * be retrieved through this method. * <p><em>Reads from the list are destructive!</em> Calling this method will * clear the list of captured exceptions. However, calling * {@link #write(java.util.List)} or {@link #write(java.lang.Object)} * multiple times with no intervening call to this method will not clear the * list of captured exceptions, but rather add to it if further exceptions * are thrown.</p> * * @return A list of exceptions that would have been thrown during any and * all read operations since the last call to this method */
public List<CsvException> getCapturedExceptions() { List<CsvException> intermediate = capturedExceptions; capturedExceptions = new ArrayList<>(); return intermediate; }
Sets the locale for all error messages.
Params:
  • errorLocale – Locale for error messages. If null, the default locale is used.
Since:4.0
/** * Sets the locale for all error messages. * * @param errorLocale Locale for error messages. If null, the default locale * is used. * @since 4.0 */
public void setErrorLocale(Locale errorLocale) { this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault()); } }