View Javadoc
1   package com.github.mygreen.supercsv.io;
2   
3   import java.io.IOException;
4   import java.io.Reader;
5   import java.io.UncheckedIOException;
6   import java.lang.reflect.Method;
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.NoSuchElementException;
12  import java.util.Optional;
13  import java.util.Spliterator;
14  import java.util.Spliterators;
15  import java.util.stream.Collectors;
16  import java.util.stream.Stream;
17  import java.util.stream.StreamSupport;
18  
19  import org.supercsv.cellprocessor.ift.CellProcessor;
20  import org.supercsv.exception.SuperCsvCellProcessorException;
21  import org.supercsv.exception.SuperCsvException;
22  import org.supercsv.exception.SuperCsvReflectionException;
23  import org.supercsv.io.AbstractCsvReader;
24  import org.supercsv.io.CsvBeanReader;
25  import org.supercsv.io.ITokenizer;
26  import org.supercsv.prefs.CsvPreference;
27  import org.supercsv.util.BeanInterfaceProxy;
28  import org.supercsv.util.CsvContext;
29  import org.supercsv.util.MethodCache;
30  
31  import com.github.mygreen.supercsv.builder.BeanMapping;
32  import com.github.mygreen.supercsv.builder.CallbackMethod;
33  import com.github.mygreen.supercsv.exception.SuperCsvBindingException;
34  import com.github.mygreen.supercsv.exception.SuperCsvNoMatchColumnSizeException;
35  import com.github.mygreen.supercsv.exception.SuperCsvNoMatchHeaderException;
36  import com.github.mygreen.supercsv.exception.SuperCsvRowException;
37  import com.github.mygreen.supercsv.validation.CsvBindingErrors;
38  import com.github.mygreen.supercsv.validation.CsvError;
39  import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
40  import com.github.mygreen.supercsv.validation.CsvValidator;
41  import com.github.mygreen.supercsv.validation.ValidationContext;
42  
43  /**
44   * アノテーションを元にCSVファイルを読み込むための抽象クラス。
45   * 
46   * @param <T> マッピング対象のBeanのクラスタイプ
47   *
48   * @see CsvBeanReader
49   * @version 2.3
50   * @since 2.1
51   * @author T.TSUCHIE
52   *
53   */
54  public abstract class AbstractCsvAnnotationBeanReader<T> extends AbstractCsvReader {
55      
56      /**
57       * Beanのマッピング情報のキャッシュ。
58       * ・組み立てたCellProcessorをキャッシュしておきます。
59       */
60      protected BeanMappingCache<T> beanMappingCache;
61      
62      /** temporary storage of processed columns to be mapped to the bean */
63      protected final List<Object> processedColumns = new ArrayList<>();
64      
65      /** cache of methods for mapping from columns to fields */
66      protected final MethodCache cache = new MethodCache();
67      
68      /** exception converter. */
69      protected CsvExceptionConverteronverter.html#CsvExceptionConverter">CsvExceptionConverter exceptionConverter = new CsvExceptionConverter();
70      
71      /** processing error messages. */
72      protected final List<String> errorMessages = new ArrayList<>();
73      
74      /** validator */
75      protected final List<CsvValidator<T>> validators = new ArrayList<>();
76      
77      public AbstractCsvAnnotationBeanReader(final Reader reader, final CsvPreference preference) {
78          super(reader, preference);
79      }
80      
81      public AbstractCsvAnnotationBeanReader(final ITokenizer tokenizer, final CsvPreference preference) {
82          super(tokenizer, preference);
83      }
84      
85      /**
86       * 1レコード分を読み込みます。
87       * 
88       * @return Beanのレコード。読み込むレコードがない場合は、nullを返します。
89       * 
90       * @throws IOException レコードの読み込みに失敗した場合。
91       * @throws SuperCsvNoMatchColumnSizeException レコードのカラムサイズに問題がある場合
92       * @throws SuperCsvBindingException セルの値に問題がある場合
93       * @throws SuperCsvException 設定など、その他に問題がある場合
94       * 
95       */
96      public T read() throws IOException {
97          
98          if(readRow()) {
99              
100             final T bean = instantiateBean(beanMappingCache.getOriginal().getType());
101             final CsvBindingErrorsBindingErrors.html#CsvBindingErrors">CsvBindingErrors bindingErrors = new CsvBindingErrors(beanMappingCache.getOriginal().getType());
102             
103             final CsvContext context = new CsvContext(getLineNumber(), getRowNumber(), 1);
104             context.setRowSource(new ArrayList<Object>(processedColumns));
105             
106             Optional<SuperCsvRowException> rowException = Optional.empty();
107             try {
108                 executeCellProcessor(processedColumns, getColumns(), beanMappingCache.getCellProcessorsForReading(), context);
109                 
110             } catch(SuperCsvRowException e) {
111                 /*
112                  * カラムごとのCellProcessorのエラーの場合、別なValidatorで値を検証するために、
113                  * 後から判定を行うようにする。
114                  */
115                 rowException = Optional.of(e);
116                 
117                 final List<CsvError> errors = exceptionConverter.convert(e, beanMappingCache.getOriginal());
118                 bindingErrors.addAllErrors(errors);
119                 
120             } catch(SuperCsvException e) {
121                 errorMessages.addAll(exceptionConverter.convertAndFormat(e, beanMappingCache.getOriginal()));
122                 throw e;
123             }
124             
125             // コールバックメソッドの実行(読み込み前)
126             for(CallbackMethod callback : beanMappingCache.getOriginal().getPreReadMethods()) {
127                 callback.invoke(bean, context, bindingErrors, beanMappingCache.getOriginal());
128             }
129             
130             // beanへのマッピング
131             populateBean(bean, beanMappingCache.getNameMapping(), bindingErrors);
132             
133             // Bean(レコード)の入力値検証
134             for(CsvValidator<T> recordValidator : validators) {
135                 recordValidator.validate(bean, bindingErrors, new ValidationContext<>(context, beanMappingCache.getOriginal()));
136             }
137             
138             // コールバックメソッドの実行(読み込み後)
139             for(CallbackMethod callback : beanMappingCache.getOriginal().getPostReadMethods()) {
140                 callback.invoke(bean, context, bindingErrors, beanMappingCache.getOriginal());
141             }
142             
143             // エラーメッセージの変換
144             processErrors(bindingErrors, context, rowException);
145             
146             return bean;
147             
148         }
149         
150         return null; // EOF
151         
152         
153     }
154     
155     /**
156      * 成功時、例外発生時の処理を指定して、1レコード分を読み込みます。
157      * 
158      * @since 2.3
159      * @param successHandler 読み込み成功時の処理の実装。
160      * @param errorHandler CSVに関する例外発生時の処理の実装。
161      * @return CSVの読み込み処理ステータスを返します。
162      * @throws IOException 致命的なレコードの読み込みに失敗した場合にスローされます。
163      */
164     public CsvReadStatus read(final CsvSuccessHandler<T> successHandler, final CsvErrorHandler errorHandler) throws IOException {
165         
166         try {
167             final T bean = read();
168             if(bean != null) {
169                 successHandler.onSuccess(bean);
170                 return CsvReadStatus.SUCCESS;
171             } else {
172                 return CsvReadStatus.EOF;
173             }
174         
175         } catch(SuperCsvException e) {
176             errorHandler.onError(e);
177             return CsvReadStatus.ERROR;
178             
179         }
180         
181     }
182     
183     /**
184      * {@link Stream} を返します。要素はCSVの行をBeanにマッピングしたオブジェクトです。
185      * <p>読み込む際には例外 {@link SuperCsvException} / {@link UncheckedIOException} が発生する可能性があります(読み込みを行った {@link Stream} メソッドからスローされます)。</p>
186      * <p>読み込み時にスローされた {@link IOException} は、{@link UncheckedIOException} にラップされます。</p>
187      * 
188      * @since 2.3
189      * @return 各レコードをBeanに変換した {@link Stream} を返します。
190      */
191     public Stream<T> lines() {
192         
193         Iterator<T> itr = new Iterator<T>() {
194             
195             T nextLine = null;
196             
197             @Override
198             public boolean hasNext() {
199                 if(nextLine != null) {
200                     return true;
201                     
202                 } else {
203                     try {
204                         nextLine = read();
205                         return (nextLine != null);
206                     } catch(IOException e) {
207                         throw new UncheckedIOException(e);
208                     }
209                 }
210             }
211 
212             @Override
213             public T next() {
214                 if (nextLine != null || hasNext()) {
215                     T line = nextLine;
216                     nextLine = null;
217                     return line;
218                 } else {
219                     throw new NoSuchElementException();
220                 }
221             }
222             
223         };
224         
225         return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
226                 itr, Spliterator.ORDERED | Spliterator.NONNULL), false);
227 
228     }
229     
230     /**
231      * CSVのヘッダーの検証を行います。
232      * 
233      * @param sourceHeader オリジナルのヘッダー情報。
234      * @param definedHeader アノテーションなどの定義を元にしたヘッダー情報
235      * @throws SuperCsvNoMatchColumnSizeException ヘッダーのサイズ(カラム数)がBean定義と一致しない場合。
236      * @throws SuperCsvNoMatchHeaderException ヘッダーの値がBean定義と一致しない場合。
237      */
238     protected void validateHeader(final String[] sourceHeader, final String[] definedHeader) {
239         
240         // check column size.
241         if(sourceHeader.length != definedHeader.length) {
242             final CsvContext context = new CsvContext(1, 1, 1);
243             throw new SuperCsvNoMatchColumnSizeException(sourceHeader.length, definedHeader.length, context);
244             
245         }
246         
247         // check header value
248         for(int i=0; i < sourceHeader.length; i++) {
249             if(!sourceHeader[i].equals(definedHeader[i])) {
250                 final CsvContext context = new CsvContext(1, 1, i+1);
251                 throw new SuperCsvNoMatchHeaderException(sourceHeader, definedHeader, context);
252             }
253         }
254         
255     }
256     
257     /**
258      * 行の例外情報をメッセージに変換したりします。
259      * @param bindingErrors
260      * @param context
261      * @param rowException
262      */
263     protected void processErrors(final CsvBindingErrors bindingErrors, final CsvContext context,
264             final Optional<SuperCsvRowException> rowException) {
265         if(bindingErrors.hasErrors()) {
266             final List<String> message = bindingErrors.getAllErrors().stream()
267                     .map(error -> error.format(exceptionConverter.getMessageResolver(), exceptionConverter.getMessageInterpolator()))
268                     .collect(Collectors.toList());
269             errorMessages.addAll(message);
270             
271             final SuperCsvBindingExceptionException.html#SuperCsvBindingException">SuperCsvBindingException bindingException = new SuperCsvBindingException("has binding error.", context, bindingErrors);
272             rowException.ifPresent(re -> bindingException.addAllProcessingErrors(re.getColumnErrors()));
273             
274             throw bindingException;
275         }
276     }
277     
278     /**
279      * 指定したBeanのクラスのインスタンスを作成する。
280      * 
281      * @param clazz Beanのクラスタイプ。
282      * @return Beanのインスタンス。
283      * @throws SuperCsvReflectionException Beanのインスタンスの作成に失敗した場合。
284      */
285     protected T instantiateBean(final Class<T> clazz) {
286         
287         final T bean;
288         if( clazz.isInterface() ) {
289             bean = BeanInterfaceProxy.createProxy(clazz);
290         } else {
291             try {
292                 bean = clazz.newInstance();
293             } catch(InstantiationException e) {
294                 throw new SuperCsvReflectionException(String.format(
295                     "error instantiating bean, check that %s has a default no-args constructor", clazz.getName()), e);
296             } catch(IllegalAccessException e) {
297                 throw new SuperCsvReflectionException("error instantiating bean", e);
298             }
299         }
300         
301         return bean;
302         
303     }
304     
305     /**
306      * 行の各カラムの値に対して、CellProcessorを適用します。
307      * @param destination
308      * @param source
309      * @param processors
310      * @param context
311      * @throws SuperCsvNoMatchColumnSizeException カラムサイズが定義と一致しない場合
312      * @throws SuperCsvRowException CellProcessor内で発生した例外
313      */
314     protected void executeCellProcessor(final List<Object> destination, final List<String> source,
315             final CellProcessor[] processors, final CsvContext context) {
316         
317         if(source.size() != processors.length) {
318             throw new SuperCsvNoMatchColumnSizeException(source.size(), processors.length, context);
319             
320         }
321         
322         destination.clear();
323         
324         final SuperCsvRowExceptionvRowException.html#SuperCsvRowException">SuperCsvRowException rowException = new SuperCsvRowException(
325                 String.format("row (%d) has errors column", context.getRowNumber()), context);
326         
327         for( int i = 0; i < source.size(); i++ ) {
328             
329             try {
330                 context.setColumnNumber(i + 1); // update context (columns start at 1)
331                 
332                 if( processors[i] == null ) {
333                     destination.add(source.get(i)); // no processing required
334                 } else {
335                     destination.add(processors[i].execute(source.get(i), context)); // execute the processor chain
336                 }
337             } catch(SuperCsvCellProcessorException e) {
338                 rowException.addError(e);
339                 
340                 // 各カラムでエラーがあっても、後の入力値検証で処理を続けるために、仮に値を設定する。
341                 destination.add(source.get(i));
342                 
343             } catch(SuperCsvException e) {
344                 
345                 throw e;
346                 
347             }
348         }
349         
350         if(rowException.isNotEmptyColumnErrors()) {
351             throw rowException;
352         }
353         
354     }
355     
356     /**
357      * Beanの各フィールドに対して値を設定する。
358      * @param resultBean
359      * @param nameMapping
360      * @param bindingErrors
361      */
362     protected void populateBean(final T resultBean, final String[] nameMapping, final CsvBindingErrors bindingErrors) {
363         
364         // map each column to its associated field on the bean
365         for( int i = 0; i < nameMapping.length; i++ ) {
366             final String fieldName = nameMapping[i];
367             final Object fieldValue = processedColumns.get(i);
368             
369             // don't call a set-method in the bean if there is no name mapping for the column or no result to store
370             if( fieldName == null || fieldValue == null || bindingErrors.hasFieldErrors(fieldName)) {
371                 continue;
372             }
373             
374             // invoke the setter on the bean
375             final Method setMethod = cache.getSetMethod(resultBean, fieldName, fieldValue.getClass());
376             try {
377                 setMethod.invoke(resultBean, fieldValue);
378                 
379             } catch(final Exception e) {
380                 throw new SuperCsvReflectionException(String.format("error invoking method %s()", setMethod.getName()), e);
381             }
382             
383         }
384         
385     }
386     
387     /**
388      * Beanのマッピング情報をキャッシュするクラスを取得します。
389      * @return Beanのマッピング情報をキャッシュするクラス
390      */
391     protected BeanMappingCache<T> getBeanMappingCache() {
392         return beanMappingCache;
393     }
394     
395     /**
396      * Beanのマッピング情報をキャッシュするクラスを設定します。
397      * @param beanMappingCache Beanのマッピング情報をキャッシュするクラス
398      */
399     protected void setBeanMappingCache(BeanMappingCache<T> beanMappingCache) {
400         this.beanMappingCache = beanMappingCache;
401     }
402     
403     /**
404      * Beanクラスを元に作成したヘッダー情報を取得する。
405      * @return ヘッダー一覧。
406      */
407     public String[] getDefinedHeader() {
408         return beanMappingCache.getHeader();
409     }
410     
411     /**
412      * Beanのマッピング情報を取得します。
413      * @return Beanのマッピング情報
414      */
415     public BeanMapping<T> getBeanMapping() {
416         return beanMappingCache.getOriginal();
417     }
418     
419     /**
420      * エラーメッセージを取得します。
421      * @return 処理中に発生した例外をメッセージに変換した
422      */
423     public List<String> getErrorMessages() {
424         return errorMessages;
425     }
426     
427     /**
428      * 処理中に発生した例外をメッセージに変換するクラスを取得します。
429      * @return 
430      */
431     public CsvExceptionConverter getExceptionConverter() {
432         return exceptionConverter;
433     }
434     
435     /**
436      * 処理中に発生した例外をメッセージに変換するクラスを設定します。
437      * @param exceptionConverter 独自にカスタマイズした値を設定します。
438      */
439     public void setExceptionConverter(CsvExceptionConverter exceptionConverter) {
440         this.exceptionConverter = exceptionConverter;
441     }
442     
443     /**
444      * レコードの値を検証するValidatorを追加します。
445      * @param validators {@link CsvValidator}の実装クラスを設定します。
446      */
447     @SuppressWarnings("unchecked")
448     public void addValidator(CsvValidator<T>... validators) {
449         this.validators.addAll(Arrays.asList(validators));
450     }
451     
452     /**
453      * レコードの値を検証するValidatorを取得します。
454      * @return {@link CsvValidator}の実装クラスを設定します。
455      */
456     public List<CsvValidator<T>> getValidators() {
457         return validators;
458     }
459     
460 }