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

com.github.mygreen.supercsv.io.CsvAnnotationBeanWriter Maven / Gradle / Ivy

Go to download

CSVのJavaライブラリであるSuperCSVに、アノテーション機能を追加したライブラリです。

There is a newer version: 2.3
Show newest version
package com.github.mygreen.supercsv.io;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.exception.SuperCsvException;
import org.supercsv.exception.SuperCsvReflectionException;
import org.supercsv.io.AbstractCsvWriter;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.util.CsvContext;
import org.supercsv.util.MethodCache;

import com.github.mygreen.supercsv.builder.BeanMapping;
import com.github.mygreen.supercsv.builder.BeanMappingFactory;
import com.github.mygreen.supercsv.builder.CallbackMethod;
import com.github.mygreen.supercsv.exception.SuperCsvBindingException;
import com.github.mygreen.supercsv.exception.SuperCsvRowException;
import com.github.mygreen.supercsv.validation.CsvBindingErrors;
import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
import com.github.mygreen.supercsv.validation.CsvValidator;
import com.github.mygreen.supercsv.validation.CsvError;
import com.github.mygreen.supercsv.validation.ValidationContext;

/**
 * アノテーションを元にCSVファイルを出力するためのクラス。
 *
 * @param  マッピング対象のBeanのクラスタイプ
 * @version 2.0
 * @author T.TSUCHIE
 *
 */
public class CsvAnnotationBeanWriter extends AbstractCsvWriter {
    
    /** temporary storage of bean values */
    private final List beanValues = new ArrayList<>();
    
    /** temporary storage of processed columns to be written */
    private final List processedColumns = new ArrayList<>();
    
    /** cache of methods for mapping from fields to columns */
    private final MethodCache cache = new MethodCache();
    
    private final BeanMappingCache beanMapping;
    
    /** exception converter. */
    private CsvExceptionConverter exceptionConverter = new CsvExceptionConverter();
    
    /** processing error messages. */
    private final List errorMessages = new ArrayList<>();
    
    /** validator */
    private final List> validators = new ArrayList<>();
    
    /**
     * Beanのクラスタイプを指定して、{@link CsvAnnotationBeanWriter}を作成するコンストラクタ。
     * 

{@link BufferedWriter}にラップして実行されるため、ラップする必要はありません。

* * @param beanType Beanのクラスタイプ。 * @param writer the writer * @param preference CSV preferences. * @param groups グループ情報。適用するアノテーションを切り替える際に指定します。 * @throws NullPointerException {@literal if beanType or writer or preferences are null.} */ public CsvAnnotationBeanWriter(final Class beanType, final Writer writer, final CsvPreference preference, final Class... groups) { super(writer, preference); Objects.requireNonNull(beanType, "beanType should not be null."); BeanMappingFactory factory = new BeanMappingFactory(); this.beanMapping = BeanMappingCache.create(factory.create(beanType, groups)); this.validators.addAll(beanMapping.getOriginal().getValidators()); } /** * Beanのマッピング情報を指定して、{@link CsvAnnotationBeanWriter}を作成するコンストラクタ。 *

{@link BufferedWriter}にラップして実行されるため、ラップする必要はありません。

* * @param beanMapping Beanのマッピング情報。 * @param writer the writer * @param preference the CSV preferences. * @throws NullPointerException {@literal if beanMapping or writer or preferences are null.} */ public CsvAnnotationBeanWriter(final BeanMapping beanMapping, final Writer writer, final CsvPreference preference) { super(writer, preference); Objects.requireNonNull(beanMapping, "beanMapping should not be null."); this.beanMapping = BeanMappingCache.create(beanMapping); this.validators.addAll(beanMapping.getValidators()); } /** * Beanクラスを元に作成したヘッダー情報を取得する。 *

ただし、列番号を省略され、定義がされていないカラムは、{@literal column[カラム番号]}の形式となります。

* @return ヘッダー一覧。 */ public String[] getDefinedHeader() { return beanMapping.getHeader(); } /** * ヘッダー情報を書き込みます。 *

ただし、列番号を省略され、定義がされていないカラムは、{@literal column[カラム番号]}の形式となります。

* @throws IOException ファイルの出力に失敗した場合。 */ public void writeHeader() throws IOException { super.writeHeader(getDefinedHeader()); } /** * レコードのデータを全て書き込みます。 *

ヘッダー行も自動的に処理されます。2回目以降に呼び出した場合、ヘッダー情報は書き込まれません。

* * @param sources 書き込むレコードのデータ。 * @throws NullPointerException sources is null. * @throws IOException レコードの出力に失敗した場合。 * @throws SuperCsvBindingException セルの値に問題がある場合 * @throws SuperCsvException 設定など、その他に問題がある場合 * */ public void writeAll(final Collection sources) throws IOException { writeAll(sources); } /** * レコードのデータを全て書き込みます。 *

ヘッダー行も自動的に処理されます。2回目以降に呼び出した場合、ヘッダー情報は書き込まれません。

* * @param sources 書き込むレコードのデータ。 * @param continueOnError continueOnError レコードの処理中に、 * 例外{@link SuperCsvBindingException}が発生しても、続行するかどうか指定します。 * trueの場合、例外が発生しても、次の処理を行います。 * @throws NullPointerException sources is null. * @throws IOException レコードの出力に失敗した場合。 * @throws SuperCsvBindingException セルの値に問題がある場合 * @throws SuperCsvException 設定など、その他に問題がある場合 * */ public void writeAll(final Collection sources, final boolean continueOnError) throws IOException { Objects.requireNonNull(sources, "sources should not be null."); if(beanMapping.getOriginal().isHeader() && getLineNumber() == 0) { writeHeader(); } for(T record : sources) { try { write(record); } catch(SuperCsvBindingException e) { if(!continueOnError) { throw e; } } } } /** * レコードを書き込みます。 * * @param source 書き込むレコード。 * @throws NullPointerException source is null. * @throws IOException レコードの出力に失敗した場合。 * @throws SuperCsvException レコードの値に問題がある場合 * */ public void write(final T source) throws IOException { Objects.requireNonNull(source, "the bean to write should not be null."); // update the current row/line numbers super.incrementRowAndLineNo(); // extract the bean values extractBeanValues(source, beanMapping.getNameMapping()); final CsvContext context = new CsvContext(getLineNumber(), getRowNumber(), 1); context.setRowSource(new ArrayList(beanValues)); final CsvBindingErrors bindingErrors = new CsvBindingErrors(beanMapping.getOriginal().getType()); // コールバックメソッドの実行(書き込み前) for(CallbackMethod callback : beanMapping.getOriginal().getPreWriteMethods()) { callback.invoke(source, context, bindingErrors, beanMapping.getOriginal()); } Optional rowException = Optional.empty(); try { executeCellProcessors(processedColumns, beanValues, beanMapping.getCellProcessorsForWriting(), context); } catch(SuperCsvRowException e) { /* * カラムごとのCellProcessorのエラーの場合、別なValidatorで値を検証するために、 * 後から判定を行うようにする。 */ rowException = Optional.of(e); final List errors = exceptionConverter.convert(e, beanMapping.getOriginal()); bindingErrors.addAllErrors(errors); } catch(SuperCsvException e) { // convert exception and format to message. errorMessages.addAll(exceptionConverter.convertAndFormat(e, beanMapping.getOriginal())); throw e; } // レコード、Beanの入力値検証 if(!beanMapping.getOriginal().isSkipValidationOnWrite()) { for(CsvValidator validator : validators) { validator.validate(source, bindingErrors, new ValidationContext<>(context, beanMapping.getOriginal())); } } // エラーメッセージの変換 processErrors(bindingErrors, context, rowException); // write the list super.writeRow(processedColumns); // コールバックメソッドの実行(書き込み後) for(CallbackMethod callback : beanMapping.getOriginal().getPostWriteMethods()) { callback.invoke(source, context, bindingErrors, beanMapping.getOriginal()); } // エラーメッセージの変換 processErrors(bindingErrors, context, rowException); } private void processErrors(final CsvBindingErrors bindingErrors, final CsvContext context, final Optional rowException) { if(bindingErrors.hasErrors()) { final List message = bindingErrors.getAllErrors().stream() .map(error -> error.format(exceptionConverter.getMessageResolver(), exceptionConverter.getMessageInterpolator())) .collect(Collectors.toList()); errorMessages.addAll(message); final SuperCsvBindingException bindingException = new SuperCsvBindingException("has binding error.", context, bindingErrors); rowException.ifPresent(re -> bindingException.addAllProcessingErrors(re.getColumnErrors())); throw bindingException; } } /** * Extracts the bean values, using the supplied name mapping array. * * @param source * the bean * @param nameMapping * the name mapping * @throws NullPointerException * if source or nameMapping are null * @throws SuperCsvReflectionException * if there was a reflection exception extracting the bean value */ private void extractBeanValues(final Object source, final String[] nameMapping) throws SuperCsvReflectionException { Objects.requireNonNull(nameMapping, "the nameMapping array can't be null as it's used to map from fields to columns"); beanValues.clear(); for( int i = 0; i < nameMapping.length; i++ ) { final String fieldName = nameMapping[i]; if( fieldName == null ) { beanValues.add(null); // assume they always want a blank column } else { Method getMethod = cache.getGetMethod(source, fieldName); try { beanValues.add(getMethod.invoke(source)); } catch(final Exception e) { throw new SuperCsvReflectionException(String.format("error extracting bean value for field %s", fieldName), e); } } } } /** * * @see Util#executeCellProcessors(List, List, CellProcessor[], int, int) */ private void executeCellProcessors(final List destination, final List source, final CellProcessor[] processors, final CsvContext context) { destination.clear(); final SuperCsvRowException rowException = new SuperCsvRowException( String.format("row (%d) has errors column", context.getRowNumber()), context); for( int i = 0; i < source.size(); i++ ) { try { context.setColumnNumber(i + 1); // update context (columns start at 1) if( processors[i] == null ) { destination.add(source.get(i)); // no processing required } else { destination.add(processors[i].execute(source.get(i), context)); // execute the processor chain } } catch(SuperCsvCellProcessorException e) { rowException.addError(e); // 各カラムでエラーがあっても、後の入力値検証で処理を続けるために、仮に値を設定する。 destination.add(source.get(i)); } catch(SuperCsvException e) { throw e; } } if(rowException.isNotEmptyColumnErrors()) { throw rowException; } } /** * Beanのマッピング情報を取得します。 * @return Beanのマッピング情報 */ public BeanMapping getBeanMapping() { return beanMapping.getOriginal(); } /** * エラーメッセージを取得します。 * @return 処理中に発生した例外をメッセージに変換した */ public List getErrorMessages() { return errorMessages; } /** * 処理中に発生した例外をメッセージに変換するクラスを取得します。 * @return */ public CsvExceptionConverter getExceptionConverter() { return exceptionConverter; } /** * 処理中に発生した例外をメッセージに変換するクラスを設定します。 * @param exceptionConverter 独自にカスタマイズした値を設定します。 */ public void setExceptionConverter(CsvExceptionConverter exceptionConverter) { this.exceptionConverter = exceptionConverter; } /** * レコード用の値を検証するValidatorを追加します。 * @param validators {@link CsvValidator}の実装クラスを設定します。 */ @SuppressWarnings("unchecked") public void addValidator(CsvValidator... validators ) { this.validators.addAll(Arrays.asList(validators)); } /** * レコードの値を検証するValidatorを取得します。 * @return {@link CsvValidator}の実装クラスを設定します。 */ public List> getValidators() { return validators; } }