View Javadoc
1   package com.github.mygreen.supercsv.io;
2   
3   import java.io.IOException;
4   import java.io.Writer;
5   import java.lang.reflect.Method;
6   import java.util.ArrayList;
7   import java.util.Arrays;
8   import java.util.Collections;
9   import java.util.List;
10  import java.util.Objects;
11  import java.util.Optional;
12  import java.util.stream.Collectors;
13  
14  import org.supercsv.cellprocessor.ift.CellProcessor;
15  import org.supercsv.exception.SuperCsvCellProcessorException;
16  import org.supercsv.exception.SuperCsvException;
17  import org.supercsv.exception.SuperCsvReflectionException;
18  import org.supercsv.io.AbstractCsvWriter;
19  import org.supercsv.io.CsvBeanWriter;
20  import org.supercsv.prefs.CsvPreference;
21  import org.supercsv.util.CsvContext;
22  import org.supercsv.util.MethodCache;
23  
24  import com.github.mygreen.supercsv.builder.BeanMapping;
25  import com.github.mygreen.supercsv.builder.CallbackMethod;
26  import com.github.mygreen.supercsv.builder.ColumnMapping;
27  import com.github.mygreen.supercsv.builder.FixedSizeColumnProperty;
28  import com.github.mygreen.supercsv.cellprocessor.conversion.PaddingProcessor;
29  import com.github.mygreen.supercsv.exception.SuperCsvBindingException;
30  import com.github.mygreen.supercsv.exception.SuperCsvRowException;
31  import com.github.mygreen.supercsv.validation.CsvBindingErrors;
32  import com.github.mygreen.supercsv.validation.CsvError;
33  import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
34  import com.github.mygreen.supercsv.validation.CsvValidator;
35  import com.github.mygreen.supercsv.validation.ValidationContext;
36  
37  /**
38   * アノテーションを元にCSVファイルを書き出すための抽象クラス。
39   *
40   * @param <T> マッピング対象のBeanのクラスタイプ
41   * 
42   * @see CsvBeanWriter
43   * @version 2.3
44   * @since 2.1
45   * @author T.TSUCHIE
46   *
47   */
48  public abstract class AbstractCsvAnnotationBeanWriter<T> extends AbstractCsvWriter {
49      
50      /**
51       * Beanのマッピング情報のキャッシュ。
52       * ・組み立てたCellProcessorをキャッシュしておきます。
53       */
54      protected BeanMappingCache<T> beanMappingCache;
55      
56      /** temporary storage of bean values */
57      protected final List<Object> beanValues = new ArrayList<>();
58      
59      /** temporary storage of processed columns to be written */
60      protected final List<Object> processedColumns = new ArrayList<>();
61      
62      /** cache of methods for mapping from fields to columns */
63      protected final MethodCache cache = new MethodCache();
64      
65      /** exception converter. */
66      protected CsvExceptionConverteronverter.html#CsvExceptionConverter">CsvExceptionConverter exceptionConverter = new CsvExceptionConverter();
67      
68      /** processing error messages. */
69      protected final List<String> errorMessages = new ArrayList<>();
70      
71      /** validator */
72      protected final List<CsvValidator<T>> validators = new ArrayList<>();
73      
74      public AbstractCsvAnnotationBeanWriter(final Writer writer, final CsvPreference preference) {
75          super(writer, preference);
76          
77      }
78      
79      /**
80       * レコードを書き込みます。
81       * 
82       * @param source 書き込むレコード。
83       * @throws NullPointerException source is null.
84       * @throws IOException レコードの出力に失敗した場合。
85       * @throws SuperCsvException レコードの値に問題がある場合
86       * 
87       */
88      public void write(final T source) throws IOException {
89          
90          Objects.requireNonNull(source, "the bean to write should not be null.");
91          
92          // update the current row/line numbers
93          incrementRowAndLineNo();
94          
95          final CsvContext context = new CsvContext(getLineNumber(), getRowNumber(), 1);
96          context.setRowSource(Collections.emptyList());  // 空の値を入れる
97          
98          final CsvBindingErrorsBindingErrors.html#CsvBindingErrors">CsvBindingErrors bindingErrors = new CsvBindingErrors(beanMappingCache.getOriginal().getType());
99          
100         // コールバックメソッドの実行(書き込み前)
101         for(CallbackMethod callback : beanMappingCache.getOriginal().getPreWriteMethods()) {
102             callback.invoke(source, context, bindingErrors, beanMappingCache.getOriginal());
103         }
104         
105         // extract the bean values
106         extractBeanValues(source, beanMappingCache.getNameMapping());
107         context.setRowSource(new ArrayList<Object>(beanValues));
108         
109         Optional<SuperCsvRowException> rowException = Optional.empty();
110         try {
111             executeCellProcessors(processedColumns, beanValues, beanMappingCache.getCellProcessorsForWriting(), context);
112             
113         } catch(SuperCsvRowException e) {
114             /*
115              * カラムごとのCellProcessorのエラーの場合、別なValidatorで値を検証するために、
116              * 後から判定を行うようにする。
117              */
118             rowException = Optional.of(e);
119             
120             final List<CsvError> errors = exceptionConverter.convert(e, beanMappingCache.getOriginal());
121             bindingErrors.addAllErrors(errors);
122             
123         } catch(SuperCsvException e) {
124             // convert exception and format to message.
125             errorMessages.addAll(exceptionConverter.convertAndFormat(e, beanMappingCache.getOriginal()));
126             throw e;
127         }
128         
129         // レコード、Beanの入力値検証
130         if(!beanMappingCache.getOriginal().isSkipValidationOnWrite()) {
131             for(CsvValidator<T> validator : validators) {
132                 validator.validate(source, bindingErrors, new ValidationContext<>(context, beanMappingCache.getOriginal()));
133             }
134         }
135         
136         // エラーメッセージの変換
137         processErrors(bindingErrors, context, rowException);
138         
139         // write the list
140         writeRow(processedColumns);
141         
142         // コールバックメソッドの実行(書き込み後)
143         for(CallbackMethod callback : beanMappingCache.getOriginal().getPostWriteMethods()) {
144             callback.invoke(source, context, bindingErrors, beanMappingCache.getOriginal());
145         }
146         
147         // エラーメッセージの変換
148         processErrors(bindingErrors, context, rowException);
149         
150     }
151     
152     /**
153      * 例外発生時の処理を指定して、1レコード分を書き込みます。
154      * 
155      * @since 2.3
156      * @param source 書き込むレコードのデータ。
157      * @param errorHandler  CSVに関する例外発生時の処理の実装。
158      * @return CSVの書き込み処理ステータスを返します。
159      * @throws IOException
160      */
161     public CsvWriteStatus write(final T source, final CsvErrorHandler errorHandler) throws IOException {
162         
163         try {
164             write(source);
165             return CsvWriteStatus.SUCCESS;
166             
167         } catch(SuperCsvException e) {
168             errorHandler.onError(e);
169             return CsvWriteStatus.ERROR;
170             
171         }
172         
173     }
174     
175     /**
176      * 行の例外情報をメッセージに変換したりします。
177      * @param bindingErrors
178      * @param context
179      * @param rowException
180      */
181     protected void processErrors(final CsvBindingErrors bindingErrors, final CsvContext context,
182             final Optional<SuperCsvRowException> rowException) {
183         
184         if(bindingErrors.hasErrors()) {
185             final List<String> message = bindingErrors.getAllErrors().stream()
186                     .map(error -> error.format(exceptionConverter.getMessageResolver(), exceptionConverter.getMessageInterpolator()))
187                     .collect(Collectors.toList());
188             errorMessages.addAll(message);
189             
190             final SuperCsvBindingExceptionException.html#SuperCsvBindingException">SuperCsvBindingException bindingException = new SuperCsvBindingException("has binding error.", context, bindingErrors);
191             rowException.ifPresent(re -> bindingException.addAllProcessingErrors(re.getColumnErrors()));
192             
193             throw bindingException;
194             
195         }
196     }
197     
198     /**
199      * Extracts the bean values, using the supplied name mapping array.
200      * 
201      * @param source
202      *            the bean
203      * @param nameMapping
204      *            the name mapping
205      * @throws NullPointerException
206      *             if source or nameMapping are null
207      * @throws SuperCsvReflectionException
208      *             if there was a reflection exception extracting the bean value
209      */
210     protected void extractBeanValues(final Object source, final String[] nameMapping) throws SuperCsvReflectionException {
211         
212         Objects.requireNonNull(nameMapping, "the nameMapping array can't be null as it's used to map from fields to columns");
213         
214         beanValues.clear();
215         
216         for( int i = 0; i < nameMapping.length; i++ ) {
217             
218             final String fieldName = nameMapping[i];
219             
220             if( fieldName == null ) {
221                 beanValues.add(null); // assume they always want a blank column
222                 
223             } else {
224                 Method getMethod = cache.getGetMethod(source, fieldName);
225                 try {
226                     beanValues.add(getMethod.invoke(source));
227                 }
228                 catch(final Exception e) {
229                     throw new SuperCsvReflectionException(String.format("error extracting bean value for field %s",
230                         fieldName), e);
231                 }
232             }
233             
234         }
235         
236     }
237     
238     /**
239      * 行の各カラムの値に対して、CellProcessorを適用します。
240      * 
241      * @param destination CellProcessorで処理した結果のカラムの値。
242      * @param source CellProcessで処理前のカラムの値。
243      * @param processors 適用するCellProcesor。
244      * @param context CSVカラム。
245      */
246     protected void executeCellProcessors(final List<Object> destination, final List<?> source,
247             final CellProcessor[] processors, final CsvContext context) {
248         
249         destination.clear();
250         
251         final SuperCsvRowExceptionvRowException.html#SuperCsvRowException">SuperCsvRowException rowException = new SuperCsvRowException(
252                 String.format("row (%d) has errors column", context.getRowNumber()), context);
253         for( int i = 0; i < source.size(); i++ ) {
254             
255             try {
256                 context.setColumnNumber(i + 1); // update context (columns start at 1)
257                 
258                 if( processors[i] == null ) {
259                     destination.add(executeNonCellProcessor(source.get(i), context)); // no processing required
260                 } else {
261                     destination.add(processors[i].execute(source.get(i), context)); // execute the processor chain
262                 }
263             } catch(SuperCsvCellProcessorException e) {
264                 rowException.addError(e);
265                 
266                 // 各カラムでエラーがあっても、後の入力値検証で処理を続けるために、仮に値を設定する。
267                 destination.add(source.get(i));
268                 
269             } catch(SuperCsvException e) {
270                 throw e;
271             }
272         }
273         
274         if(rowException.isNotEmptyColumnErrors()) {
275             throw rowException;
276         }
277         
278     }
279     
280     /**
281      * CellProcessorを持たないカラムの値を処理します。
282      * @param value 処理前のカラムの値。
283      * @param context CSV情報。
284      * @return 処理後のカラムの値。
285      */
286     protected Object executeNonCellProcessor(Object value, CsvContext context) {
287         
288         if (value != null) {
289             return value;
290         }
291         
292         Optional<ColumnMapping> columnMapping = beanMappingCache.getOriginal().getColumnMapping(context.getColumnNumber());
293         if (!columnMapping.isPresent()) {
294             return value;
295         }
296         
297         /*
298          * 部分的なカラムかつ、固定長カラムの場合は、CellProcessorは存在しないため、
299          * null -> 空文字として、独自にパディングする。
300          */
301         if (columnMapping.get().isPartialized() && columnMapping.get().getFixedSizeProperty() != null) {
302             FixedSizeColumnProperty fixedSizeProperty = columnMapping.get().getFixedSizeProperty();
303             PaddingProcessor paddingProcessor = columnMapping.get().getFixedSizeProperty().getPaddingProcessor();
304             return paddingProcessor.pad("",
305                     fixedSizeProperty.getSize(),
306                     fixedSizeProperty.getPadChar(),
307                     fixedSizeProperty.isRightAlign(),
308                     fixedSizeProperty.isChopped());
309             
310         }
311         
312         return value;
313     }
314     
315     /**
316      * Beanクラスを元に作成したヘッダー情報を取得する。
317      * <p>ただし、列番号を省略され、定義がされていないカラムは、{@literal column[カラム番号]}の形式となります。</p>
318      * @return ヘッダー一覧。
319      */
320     public String[] getDefinedHeader() {
321         return beanMappingCache.getHeader();
322     }
323     
324     /**
325      * Beanのマッピング情報を取得します。
326      * @return Beanのマッピング情報
327      */
328     public BeanMapping<T> getBeanMapping() {
329         return beanMappingCache.getOriginal();
330     }
331     
332     /**
333      * エラーメッセージを取得します。
334      * @return 処理中に発生した例外をメッセージに変換した
335      */
336     public List<String> getErrorMessages() {
337         return errorMessages;
338     }
339     
340     /**
341      * 処理中に発生した例外をメッセージに変換するクラスを取得します。
342      * @return 
343      */
344     public CsvExceptionConverter getExceptionConverter() {
345         return exceptionConverter;
346     }
347     
348     /**
349      * 処理中に発生した例外をメッセージに変換するクラスを設定します。
350      * @param exceptionConverter 独自にカスタマイズした値を設定します。
351      */
352     public void setExceptionConverter(CsvExceptionConverter exceptionConverter) {
353         this.exceptionConverter = exceptionConverter;
354     }
355     
356     /**
357      * レコード用の値を検証するValidatorを追加します。
358      * @param validators {@link CsvValidator}の実装クラスを設定します。
359      */
360     @SuppressWarnings("unchecked")
361     public void addValidator(CsvValidator<T>... validators ) {
362         this.validators.addAll(Arrays.asList(validators));
363     }
364     
365     /**
366      * レコードの値を検証するValidatorを取得します。
367      * @return {@link CsvValidator}の実装クラスを設定します。
368      */
369     public List<CsvValidator<T>> getValidators() {
370         return validators;
371     }
372     
373     
374     
375 }