View Javadoc
1   package com.github.mygreen.supercsv.builder;
2   
3   import java.lang.reflect.Field;
4   import java.lang.reflect.Method;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.List;
8   import java.util.Objects;
9   import java.util.Optional;
10  import java.util.stream.Collectors;
11  
12  import org.supercsv.cellprocessor.ift.CellProcessor;
13  import org.supercsv.exception.SuperCsvReflectionException;
14  
15  import com.github.mygreen.supercsv.annotation.CsvBean;
16  import com.github.mygreen.supercsv.annotation.CsvColumn;
17  import com.github.mygreen.supercsv.annotation.CsvPartial;
18  import com.github.mygreen.supercsv.annotation.CsvPostRead;
19  import com.github.mygreen.supercsv.annotation.CsvPostWrite;
20  import com.github.mygreen.supercsv.annotation.CsvPreRead;
21  import com.github.mygreen.supercsv.annotation.CsvPreWrite;
22  import com.github.mygreen.supercsv.annotation.DefaultGroup;
23  import com.github.mygreen.supercsv.exception.SuperCsvInvalidAnnotationException;
24  import com.github.mygreen.supercsv.localization.MessageBuilder;
25  import com.github.mygreen.supercsv.validation.CsvValidator;
26  
27  /**
28   * BeanからCSVのマッピング情報を作成するクラス。
29   * 
30   * @version 2.1
31   * @author T.TSUCHIE
32   *
33   */
34  public class BeanMappingFactory {
35      
36      private Configurationfiguration.html#Configuration">Configuration configuration = new Configuration();
37      
38      /**
39       * デフォルトコンストラクタ
40       */
41      public BeanMappingFactory() {
42          
43      }
44      
45      /**
46       * Beanクラスから、CSVのマッピング情報を作成します。
47       * 
48       * @param <T> Beanのタイプ
49       * @param beanType 作成元のBeanクラス。
50       * @param groups グループ情報。
51       *              アノテーションを指定したグループで切り替える際に指定します。
52       *              何も指定しない場合は、デフォルトグループの{@link DefaultGroup}のクラスが指定されたとして処理します。
53       * @return CSVのマッピング情報。
54       * @throws NullPointerException {@literal beanType == null.}
55       * @throws SuperCsvInvalidAnnotationException アノテーションの定義が不正な場合。
56       */
57      public <T> BeanMapping<T> create(final Class<T> beanType, final Class<?>... groups) {
58          
59          Objects.requireNonNull(beanType);
60          
61          final BeanMapping<T> beanMapping = new BeanMapping<>(beanType);
62          beanMapping.setConfiguration(configuration);
63          
64          // アノテーション @CsvBeanの取得
65          final CsvBean beanAnno = beanType.getAnnotation(CsvBean.class);
66          if(beanAnno == null) {
67              throw new SuperCsvInvalidAnnotationException(beanAnno, MessageBuilder.create("anno.notFound")
68                          .varWithClass("property", beanType)
69                          .varWithAnno("anno", CsvBean.class)
70                          .format());
71          }
72          
73          // ヘッダーの設定情報の組み立て
74          buildHeaderMapper(beanMapping, beanAnno);
75          
76          // 入力値検証の設定を組み立てます。
77          buildValidators(beanMapping, beanAnno, groups);
78          
79          // アノテーション @CsvColumn を元にしたカラム情報の組み立て
80          buildColumnMappingList(beanMapping, beanType, groups);
81          
82          // コールバックメソッドの設定
83          buildCallbackMethods(beanMapping, beanType, beanAnno);
84          
85          return beanMapping;
86      }
87      
88      /**
89       * ヘッダーのマッピングの処理や設定を組み立てます。
90       * 
91       * @param <T> Beanのタイプ
92       * @param beanMapping Beanのマッピング情報
93       * @param beanAnno アノテーション{@literal @CsvBean}のインタンス
94       */
95      protected <T> void buildHeaderMapper(final BeanMapping<T> beanMapping, final CsvBean beanAnno) {
96          
97          final HeaderMapper../com/github/mygreen/supercsv/builder/HeaderMapper.html#HeaderMapper">HeaderMapper headerMapper = (HeaderMapper) configuration.getBeanFactory().create(beanAnno.headerMapper());
98          beanMapping.setHeaderMapper(headerMapper);
99          
100         beanMapping.setHeader(beanAnno.header());
101         beanMapping.setValidateHeader(beanAnno.validateHeader());
102         
103     }
104     
105     /**
106      * 入力値検証の設定を組み立てます。
107      * 
108      * @param <T> Beanのタイプ
109      * @param beanMapping Beanのマッピング情報
110      * @param beanAnno アノテーション{@literal @CsvBean}のインタンス
111      * @param groups グループ情報
112      */
113     @SuppressWarnings({"unchecked"})
114     protected <T> void buildValidators(final BeanMapping<T> beanMapping, final CsvBean beanAnno, final Class<?>[] groups) {
115         
116         // CsvValidatorの取得
117         final List<CsvValidator<T>> validators = Arrays.stream(beanAnno.validators())
118                 .map(v -> (CsvValidator<T>)configuration.getBeanFactory().create(v))
119                 .collect(Collectors.toList());
120         beanMapping.addAllValidators(validators);
121         
122         beanMapping.setSkipValidationOnWrite(configuration.isSkipValidationOnWrite());
123         beanMapping.setGroups(groups);
124         
125     }
126     
127     /**
128      * アノテーション{@link CsvColumn}を元に、カラムのマッピング情報を組み立てる。
129      * 
130      * @param <T> Beanのタイプ
131      * @param beanMapping Beanのマッピング情報
132      * @param beanType  Beanのクラスタイプ
133      * @param groups グループ情報
134      */
135     protected <T> void buildColumnMappingList(final BeanMapping<T> beanMapping, final Class<T> beanType, final Class<?>[] groups) {
136         
137         final List<ColumnMapping> columnMappingList = new ArrayList<>();
138         for(Field field : beanType.getDeclaredFields()) {
139             
140             final CsvColumn columnAnno = field.getAnnotation(CsvColumn.class);
141             if(columnAnno != null) {
142                 columnMappingList.add(createColumnMapping(field, columnAnno, groups));
143             }
144             
145         }
146         
147         // カラムの位置順の並び変えと、位置のチェック
148         columnMappingList.sort(null);
149         validateColumnAndSupplyPartialColumn(beanType, columnMappingList);
150         beanMapping.addAllColumns(columnMappingList);
151         
152     }
153     
154     /**
155      * カラム情報を組み立てる
156      * 
157      * @param field フィールド情報
158      * @param columnAnno 設定されているカラムのアノテーション
159      * @param groups グループ情報
160      * @return 組み立てたカラム
161      */
162     @SuppressWarnings({"rawtypes", "unchecked"})
163     protected ColumnMapping createColumnMapping(final Field field, final CsvColumn columnAnno, final Class<?>[] groups) {
164         
165         final FieldAccessorldAccessor.html#FieldAccessor">FieldAccessor fieldAccessor = new FieldAccessor(field, configuration.getAnnoationComparator());
166         
167         final ColumnMappingumnMapping.html#ColumnMapping">ColumnMapping columnMapping = new ColumnMapping();
168         columnMapping.setField(fieldAccessor);
169         columnMapping.setNumber(columnAnno.number());
170         
171         if(columnAnno.label().isEmpty()) {
172             columnMapping.setLabel(field.getName());
173         } else {
174             columnMapping.setLabel(columnAnno.label());
175         }
176         
177         // ProcessorBuilderの取得
178         ProcessorBuilder builder;
179         if(columnAnno.builder().length == 0) {
180             
181             builder = configuration.getBuilderResolver().resolve(fieldAccessor.getType());
182             if(builder == null) {
183                 // 不明なタイプの場合
184                 builder = new GeneralProcessorBuilder();
185             }
186             
187         } else {
188             // 直接Builderクラスが指定されている場合
189             try {
190                 builder = (ProcessorBuilder) configuration.getBeanFactory().create(columnAnno.builder()[0]);
191                 
192             } catch(Throwable e) {
193                 throw new SuperCsvReflectionException(
194                         String.format("Fail create instance of %s with attribute 'builderClass' of @CsvColumn",
195                                 columnAnno.builder()[0].getCanonicalName()), e);
196             }
197         }
198         
199         // CellProcessorの作成
200         columnMapping.setCellProcessorForReading(
201                 (CellProcessor)builder.buildForReading(field.getType(), fieldAccessor, configuration, groups).orElse(null));
202         
203         columnMapping.setCellProcessorForWriting(
204                 (CellProcessor)builder.buildForWriting(field.getType(), fieldAccessor, configuration,  groups).orElse(null));
205         
206         if(builder instanceof AbstractProcessorBuilder) {
207             columnMapping.setFormatter(((AbstractProcessorBuilder)builder).getFormatter(fieldAccessor, configuration));
208         }
209         
210         return columnMapping;
211         
212     }
213     
214     /**
215      * カラム情報の検証と、部分的に読み込む場合のカラム情報を補足する。
216      * @param beanType Beanのクラスタイプ
217      * @param list カラム番号の昇順に並び変えられたカラム情報。
218      */
219     protected void validateColumnAndSupplyPartialColumn(final Class<?> beanType, final List<ColumnMapping> list) {
220         
221         final Optional<CsvPartial> partialAnno = Optional.ofNullable(beanType.getAnnotation(CsvPartial.class));
222         
223         if(list.isEmpty()) {
224             throw new SuperCsvInvalidAnnotationException(MessageBuilder.create("anno.notFound")
225                     .varWithClass("property", beanType)
226                     .varWithAnno("anno", CsvColumn.class)
227                     .format());
228         }
229         
230         // 番号の重複などのチェック
231         BeanMappingFactoryHelper.validateDuplicatedColumnNumber(beanType, list);
232         
233         // 不足している番号のカラムを補完する。
234         BeanMappingFactoryHelper.supplyLackedNumberMappingColumn(beanType, list, partialAnno, new String[0]);
235         
236     }
237     
238     /**
239      * 部分的なカラムの場合の作成
240      * @param columnNumber 列番号
241      * @param partialAnno アノテーション {@literal @CsvPartial}のインスタンス
242      * @return
243      */
244     protected ColumnMapping createPartialColumnMapping(int columnNumber, final Optional<CsvPartial> partialAnno) {
245         
246         final ColumnMappingumnMapping.html#ColumnMapping">ColumnMapping columnMapping = new ColumnMapping();
247         columnMapping.setNumber(columnNumber);
248         columnMapping.setPartialized(true);
249         
250         String label = String.format("column%d", columnNumber);
251         if(partialAnno.isPresent()) {
252             for(CsvPartial.Header header : partialAnno.get().headers()) {
253                 if(header.number() == columnNumber) {
254                     label = header.label();
255                 }
256             }
257         }
258         columnMapping.setLabel(label);
259         
260         return columnMapping;
261         
262     }
263     
264     /**
265      * コールバック用メソッドの設定を組み立てます。
266      * 
267      * @param <T> Beanのタイプ
268      * @param beanMapping Beanのマッピング情報
269      * @param beanType Beanのクラスタイプ
270      * @param beanAnno {@literal CsvBean}のアノテーションのインスタンス
271      */
272     protected <T> void buildCallbackMethods(final BeanMapping<T> beanMapping, final Class<T> beanType, final CsvBean beanAnno) {
273         
274         // コールバック用のメソッドの取得
275         for(Method method : beanType.getDeclaredMethods()) {
276             
277             if(method.getAnnotation(CsvPreRead.class) != null) {
278                 beanMapping.addPreReadMethod(new CallbackMethod(method));
279             }
280             
281             if(method.getAnnotation(CsvPostRead.class) != null) {
282                 beanMapping.addPostReadMethod(new CallbackMethod(method));
283             }
284             
285             if(method.getAnnotation(CsvPreWrite.class) != null) {
286                 beanMapping.addPreWriteMethod(new CallbackMethod(method));
287             }
288             
289             if(method.getAnnotation(CsvPostWrite.class) != null) {
290                 beanMapping.addPostWriteMethod(new CallbackMethod(method));
291             }
292         }
293         
294         // リスナークラスの取得
295         final List<Object> listeners = Arrays.stream(beanAnno.listeners())
296                 .map(l -> configuration.getBeanFactory().create(l))
297                 .collect(Collectors.toList());
298         beanMapping.addAllListeners(listeners);
299         
300         for(Object listener : listeners) {
301             for(Method method : listener.getClass().getDeclaredMethods()) {
302                 if(method.getAnnotation(CsvPreRead.class) != null) {
303                     beanMapping.addPreReadMethod(new ListenerCallbackMethod(listener, method));
304                 }
305                 
306                 if(method.getAnnotation(CsvPostRead.class) != null) {
307                     beanMapping.addPostReadMethod(new ListenerCallbackMethod(listener, method));
308                 }
309                 
310                 if(method.getAnnotation(CsvPreWrite.class) != null) {
311                     beanMapping.addPreWriteMethod(new ListenerCallbackMethod(listener, method));
312                 }
313                 
314                 if(method.getAnnotation(CsvPostWrite.class) != null) {
315                     beanMapping.addPostWriteMethod(new ListenerCallbackMethod(listener, method));
316                 }
317             }
318         }
319         
320         beanMapping.getPreReadMethods().sort(null);
321         beanMapping.getPostReadMethods().sort(null);
322         beanMapping.getPreWriteMethods().sort(null);
323         beanMapping.getPostWriteMethods().sort(null);
324         
325     }
326     
327     /**
328      * システム情報を取得します。
329      * @return 既存のシステム情報を変更する際に取得します。
330      */
331     public Configuration getConfiguration() {
332         return configuration;
333     }
334     
335     /**
336      * システム情報を取得します。
337      * @param configuraton 新しくシステム情報を変更する際に設定します。
338      */
339     public void setConfiguration(Configuration configuraton) {
340         this.configuration = configuraton;
341     }
342     
343 }