BeanMappingFactory.java

  1. package com.github.mygreen.supercsv.builder;

  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import java.util.List;
  7. import java.util.Objects;
  8. import java.util.Optional;
  9. import java.util.stream.Collectors;

  10. import org.supercsv.cellprocessor.ift.CellProcessor;
  11. import org.supercsv.exception.SuperCsvReflectionException;

  12. import com.github.mygreen.supercsv.annotation.CsvBean;
  13. import com.github.mygreen.supercsv.annotation.CsvColumn;
  14. import com.github.mygreen.supercsv.annotation.CsvPartial;
  15. import com.github.mygreen.supercsv.annotation.CsvPostRead;
  16. import com.github.mygreen.supercsv.annotation.CsvPostWrite;
  17. import com.github.mygreen.supercsv.annotation.CsvPreRead;
  18. import com.github.mygreen.supercsv.annotation.CsvPreWrite;
  19. import com.github.mygreen.supercsv.annotation.DefaultGroup;
  20. import com.github.mygreen.supercsv.exception.SuperCsvInvalidAnnotationException;
  21. import com.github.mygreen.supercsv.localization.MessageBuilder;
  22. import com.github.mygreen.supercsv.validation.CsvValidator;

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