All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opencsv.bean.StatefulBeanToCsv Maven / Gradle / Ivy

There is a newer version: 5.9
Show newest version
/*
 * 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.ProcessCsvBean;
import com.opencsv.bean.exceptionhandler.CsvExceptionHandler;
import com.opencsv.bean.exceptionhandler.ExceptionHandlerThrow;
import com.opencsv.bean.util.OpencsvUtils;
import com.opencsv.bean.util.OrderedObject;
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 org.apache.commons.lang3.StringUtils;

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.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * 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.
 * 

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.

* * @param Type of the bean to be written * @author Andrew Rucker Jones * @see OpencsvUtils#determineMappingStrategy(java.lang.Class, java.util.Locale, java.lang.String) * @since 3.9 */ public class StatefulBeanToCsv { private static final char NO_CHARACTER = '\0'; /** * 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 mappingStrategy; private final Writer writer; private ICSVWriter csvwriter; private final CsvExceptionHandler exceptionHandler; private List capturedExceptions = new ArrayList<>(); private boolean orderedResults = true; private BeanExecutor executor = null; private Locale errorLocale = Locale.getDefault(); private final boolean applyQuotesToAll; private final MultiValuedMap, Field> ignoredFields; private final String profile; /** * 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 exceptionHandler Determines the exception handling behavior * @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}. * @param profile The profile to use when configuring how to interpret bean fields */ StatefulBeanToCsv(char escapechar, String lineEnd, MappingStrategy mappingStrategy, char quotechar, char separator, CsvExceptionHandler exceptionHandler, Writer writer, boolean applyQuotesToAll, MultiValuedMap, Field> ignoredFields, String profile) { this.escapechar = escapechar; this.lineEnd = lineEnd; this.mappingStrategy = mappingStrategy; this.quotechar = quotechar; this.separator = separator; this.exceptionHandler = exceptionHandler; this.writer = writer; this.applyQuotesToAll = applyQuotesToAll; this.ignoredFields = ignoredFields; this.profile = StringUtils.defaultString(profile); } /** * 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 exceptionHandler Determines the exception handling behavior * @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}. * @param profile The profile to use when configuring how to interpret bean fields */ public StatefulBeanToCsv(MappingStrategy mappingStrategy, CsvExceptionHandler exceptionHandler, boolean applyQuotesToAll, ICSVWriter csvWriter, MultiValuedMap, Field> ignoredFields, String profile) { this.mappingStrategy = mappingStrategy; this.exceptionHandler = exceptionHandler; 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; this.profile = StringUtils.defaultString(profile); } /** * 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) bean.getClass(), errorLocale, profile); } // 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 {@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> resultantLineQueue = new ArrayBlockingQueue<>(1); BlockingQueue> thrownExceptionsQueue = new LinkedBlockingQueue<>(); ProcessCsvBean proc = new ProcessCsvBean<>(++lineNumber, mappingStrategy, bean, resultantLineQueue, thrownExceptionsQueue, new TreeSet<>(), exceptionHandler); 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 o = thrownExceptionsQueue.poll(); while (o != null && o.getElement() != null) { capturedExceptions.add(o.getElement()); o = thrownExceptionsQueue.poll(); } } else { // No exception, so there really must always be a string OrderedObject result = resultantLineQueue.poll(); if (result != null && result.getElement() != null) { csvwriter.writeNext(result.getElement(), applyQuotesToAll); } } } } private void submitAllLines(Iterator beans) throws InterruptedException { while (beans.hasNext()) { T bean = beans.next(); if (bean != null) { executor.submitBean(++lineNumber, mappingStrategy, bean, exceptionHandler); } } executor.complete(); } /** * 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 beans) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { if (CollectionUtils.isNotEmpty(beans)) { write(beans.iterator()); } } /** * 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 iBeans) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { PeekingIterator beans = new PeekingIterator<>(iBeans); T firstBean = beans.peek(); if (!beans.hasNext()) { return; } // Write header if (!headerWritten) { beforeFirstWrite(firstBean); } executor = new BeanExecutor<>(orderedResults, errorLocale); 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); } finally { capturedExceptions.addAll(executor.getCapturedExceptions()); } StreamSupport.stream(executor, false) .forEach(l -> csvwriter.writeNext(l, applyQuotesToAll)); } /** * 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 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 * {@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; } /** * @return Whether or not exceptions are thrown. If they are not thrown, * they are captured and returned later via {@link #getCapturedExceptions()}. * @deprecated There is simply no need for this method. */ @Deprecated public boolean isThrowExceptions() { return exceptionHandler instanceof ExceptionHandlerThrow; } /** * 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 * {@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.

* * @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 getCapturedExceptions() { List intermediate = capturedExceptions; capturedExceptions = new ArrayList<>(); intermediate.sort(Comparator.comparingLong(CsvException::getLineNumber)); return intermediate; } /** * 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()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy