/*
* 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: - CsvDataTypeMismatchException – If a field of the bean is
annotated improperly or an unsupported data type is supposed to be
written
- CsvRequiredFieldEmptyException – If a field is marked as required,
but the source is null
/**
* 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: - CsvDataTypeMismatchException – If a field of the beans is
annotated improperly or an unsupported data type is supposed to be
written
- CsvRequiredFieldEmptyException – If a field is marked as required,
but the source is null
/**
* 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: - CsvDataTypeMismatchException – If a field of the beans is annotated improperly or an unsupported
data type is supposed to be written
- CsvRequiredFieldEmptyException – If a field is marked as required, but the source is null
/**
* 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: - CsvDataTypeMismatchException – If a field of the beans is annotated improperly or an unsupported
data type is supposed to be written
- CsvRequiredFieldEmptyException – If a field is marked as required, but the source is null
/**
* 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());
}
}