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

com.github.mygreen.supercsv.builder.BeanMappingFactory Maven / Gradle / Ivy

Go to download

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

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

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

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

import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;
import com.github.mygreen.supercsv.annotation.CsvPartial;
import com.github.mygreen.supercsv.annotation.CsvPostRead;
import com.github.mygreen.supercsv.annotation.CsvPostWrite;
import com.github.mygreen.supercsv.annotation.CsvPreRead;
import com.github.mygreen.supercsv.annotation.CsvPreWrite;
import com.github.mygreen.supercsv.annotation.DefaultGroup;
import com.github.mygreen.supercsv.exception.SuperCsvInvalidAnnotationException;
import com.github.mygreen.supercsv.localization.MessageBuilder;
import com.github.mygreen.supercsv.validation.CsvValidator;

/**
 * BeanからCSVのマッピング情報を作成するクラス。
 * 
 * @version 2.0
 * @author T.TSUCHIE
 *
 */
public class BeanMappingFactory {
    
    private Configuration configuration = new Configuration();
    
    /**
     * デフォルトコンストラクタ
     */
    public BeanMappingFactory() {
        
    }
    
    /**
     * Beanクラスから、CSVのマッピング情報を作成します。
     * 
     * @param  Beanのタイプ
     * @param beanType 作成元のBeanクラス。
     * @param groups グループ情報。
     *              アノテーションを指定したグループで切り替える際に指定します。
     *              何も指定しない場合は、デフォルトグループの{@link DefaultGroup}のクラスが指定されたとして処理します。
     * @return CSVのマッピング情報。
     * @throws NullPointerException {@literal beanType == null.}
     * @throws SuperCsvInvalidAnnotationException アノテーションの定義が不正な場合。
     */
    @SuppressWarnings({"unchecked"})
    public  BeanMapping create(final Class beanType, final Class... groups) {
        
        Objects.requireNonNull(beanType);
        
        final BeanMapping beanMapping = new BeanMapping<>(beanType);
        
        // アノテーション @CsvBeanの取得
        final CsvBean beanAnno = beanType.getAnnotation(CsvBean.class);
        if(beanAnno == null) {
            throw new SuperCsvInvalidAnnotationException(beanAnno, MessageBuilder.create("anno.notFound")
                        .varWithClass("property", beanType)
                        .varWithAnno("anno", CsvBean.class)
                        .format());
        }
        
        beanMapping.setHeader(beanAnno.header());
        beanMapping.setValidateHeader(beanAnno.validateHeader());
        
        // CsvValidatorの取得
        final List> validators = Arrays.stream(beanAnno.validators())
                .map(v -> (CsvValidator)configuration.getBeanFactory().create(v))
                .collect(Collectors.toList());
        beanMapping.addAllValidators(validators);
        
        // アノテーション @CsvColumn の取得
        final List columnMappingList = new ArrayList<>();
        for(Field field : beanType.getDeclaredFields()) {
            
            final CsvColumn columnAnno = field.getAnnotation(CsvColumn.class);
            if(columnAnno != null) {
                columnMappingList.add(createColumnMapping(field, columnAnno, configuration, groups));
            }
            
        }
        
        // カラムの位置順の並び変えと、位置のチェック
        columnMappingList.sort(null);
        validateColumnAndSupplyPartialColumn(beanType, columnMappingList, Optional.ofNullable(beanType.getAnnotation(CsvPartial.class)));
        beanMapping.addAllColumns(columnMappingList);
        
        // コールバック用のメソッドの取得
        for(Method method : beanType.getDeclaredMethods()) {
            
            if(method.getAnnotation(CsvPreRead.class) != null) {
                beanMapping.addPreReadMethod(new CallbackMethod(method));
            }
            
            if(method.getAnnotation(CsvPostRead.class) != null) {
                beanMapping.addPostReadMethod(new CallbackMethod(method));
            }
            
            if(method.getAnnotation(CsvPreWrite.class) != null) {
                beanMapping.addPreWriteMethod(new CallbackMethod(method));
            }
            
            if(method.getAnnotation(CsvPostWrite.class) != null) {
                beanMapping.addPostWriteMethod(new CallbackMethod(method));
            }
        }
        
        // リスナークラスの取得
        final List listeners = Arrays.stream(beanAnno.listeners())
                .map(l -> configuration.getBeanFactory().create(l))
                .collect(Collectors.toList());
        beanMapping.addAllListeners(listeners);
        
        for(Object listener : listeners) {
            for(Method method : listener.getClass().getDeclaredMethods()) {
                if(method.getAnnotation(CsvPreRead.class) != null) {
                    beanMapping.addPreReadMethod(new ListenerCallbackMethod(listener, method));
                }
                
                if(method.getAnnotation(CsvPostRead.class) != null) {
                    beanMapping.addPostReadMethod(new ListenerCallbackMethod(listener, method));
                }
                
                if(method.getAnnotation(CsvPreWrite.class) != null) {
                    beanMapping.addPreWriteMethod(new ListenerCallbackMethod(listener, method));
                }
                
                if(method.getAnnotation(CsvPostWrite.class) != null) {
                    beanMapping.addPostWriteMethod(new ListenerCallbackMethod(listener, method));
                }
            }
        }
        
        beanMapping.getPreReadMethods().sort(null);
        beanMapping.getPostReadMethods().sort(null);
        beanMapping.getPreWriteMethods().sort(null);
        beanMapping.getPostWriteMethods().sort(null);
        
        beanMapping.setSkipValidationOnWrite(configuration.isSkipValidationOnWrite());
        beanMapping.setGroups(groups);
        
        return beanMapping;
    }
    
    @SuppressWarnings({"rawtypes", "unchecked"})
    private ColumnMapping createColumnMapping(final Field field, final CsvColumn columnAnno,
            final Configuration config, final Class[] groups) {
        
        final FieldAccessor fieldAccessor = new FieldAccessor(field, config.getAnnoationComparator());
        
        final ColumnMapping columnMapping = new ColumnMapping();
        columnMapping.setField(fieldAccessor);
        columnMapping.setNumber(columnAnno.number());
        
        if(columnAnno.label().isEmpty()) {
            columnMapping.setLabel(field.getName());
        } else {
            columnMapping.setLabel(columnAnno.label());
        }
        
        // ProcessorBuilderの取得
        ProcessorBuilder builder;
        if(columnAnno.builder().length == 0) {
            
            builder = config.getBuilderResolver().resolve(fieldAccessor.getType());
            if(builder == null) {
                // 不明なタイプの場合
                builder = new GeneralProcessorBuilder();
            }
            
        } else {
            // 直接Builderクラスが指定されている場合
            try {
                builder = (ProcessorBuilder) config.getBeanFactory().create(columnAnno.builder()[0]);
                
            } catch(Throwable e) {
                throw new SuperCsvReflectionException(
                        String.format("Fail create instance of %s with attribute 'builderClass' of @CsvColumn",
                                columnAnno.builder()[0].getCanonicalName()), e);
            }
        }
        
        // CellProcessorの作成
        columnMapping.setCellProcessorForReading(
                (CellProcessor)builder.buildForReading(field.getType(), fieldAccessor, config, groups).orElse(null));
        
        columnMapping.setCellProcessorForWriting(
                (CellProcessor)builder.buildForWriting(field.getType(), fieldAccessor, config,  groups).orElse(null));
        
        if(builder instanceof AbstractProcessorBuilder) {
            columnMapping.setFormatter(((AbstractProcessorBuilder)builder).getFormatter(fieldAccessor, config));
        }
        
        return columnMapping;
        
    }
    
    /**
     * カラム情報の検証と、部分的に読み込む場合のカラム情報を補足する。
     * @param beanType Beanのクラスタイプ
     * @param list カラム番号の昇順に並び変えられたカラム情報。
     * @param partialAnno アノテーション{@link CsvPartial}の情報
     */
    private void validateColumnAndSupplyPartialColumn(final Class beanType, final List list, 
            final Optional partialAnno) {
        
        if(list.isEmpty()) {
            throw new SuperCsvInvalidAnnotationException(MessageBuilder.create("anno.notFound")
                    .varWithClass("property", beanType)
                    .varWithAnno("anno", CsvColumn.class)
                    .format());
        }
        
        // check duplicated column number value 
        final Set checkedNumber = new TreeSet<>();
        final Set duplicateNumbers = new TreeSet<>();
        for(ColumnMapping columnMapping : list) {
            
            if(checkedNumber.contains(columnMapping.getNumber())) {
                duplicateNumbers.add(columnMapping.getNumber());
            }
            checkedNumber.add(columnMapping.getNumber());
            
        }
        
        if(!duplicateNumbers.isEmpty()) {
            // 重複している 属性 numberが存在する場合
            throw new SuperCsvInvalidAnnotationException(MessageBuilder.create("anno.attr.duplicated")
                    .var("property", beanType.getName())
                    .varWithAnno("anno", CsvColumn.class)
                    .var("attrName", "number")
                    .var("attrValues", duplicateNumbers)
                    .format());
        }
        
        // カラム番号が1以上かのチェック
        final int minColumnNumber = list.get(0).getNumber();
        if(minColumnNumber <= 0) {
            throw new SuperCsvInvalidAnnotationException(MessageBuilder.create("anno.attr.min")
                    .var("property", beanType.getName())
                    .varWithAnno("anno", CsvColumn.class)
                    .var("attrName", "number")
                    .var("attrValue", minColumnNumber)
                    .var("min", 1)
                    .format());
            
        }
        
        // 定義されている列番号の最大値
        final int maxColumnNumber = list.get(list.size()-1).getNumber();
        
        // Beanに定義されていない欠けているカラム番号の取得
        final Set lackNumbers = new TreeSet();
        for(int i=1; i <= maxColumnNumber; i++) {
            if(!checkedNumber.contains(i)) {
                lackNumbers.add(i);
            }
        }
        
        // 定義されているカラム番号より、大きなカラム番号を持つカラム情報の補足
        if(partialAnno.isPresent()) {
            
            final int partialColumnSize = partialAnno.get().columnSize();
            if(maxColumnNumber > partialColumnSize) {
                throw new SuperCsvInvalidAnnotationException(partialAnno.get(), MessageBuilder.create("anno.CsvPartial.columSizeMin")
                        .var("property", beanType.getName())
                        .var("columnSize", partialColumnSize)
                        .var("maxColumnNumber", maxColumnNumber)
                        .format());
                
            }
            
            if(maxColumnNumber < partialColumnSize) {
                for(int i= maxColumnNumber+1; i <= partialColumnSize; i++) {
                    lackNumbers.add(i);
                }
            }
            
        }
        
        // 不足分のカラムがある場合は、部分的な読み書き用カラムとして追加する
        if(lackNumbers.size() > 0) {
            
            for(int number : lackNumbers) {
                list.add(createPartialColumnMapping(number, partialAnno));
            }
            
            list.sort(null);
        }
        
    }
    
    /**
     * 部分的なカラムの場合の作成
     * @param columnNumber 列番号
     * @param partialAnno
     * @return
     */
    private ColumnMapping createPartialColumnMapping(int columnNumber, final Optional partialAnno) {
        
        final ColumnMapping columnMapping = new ColumnMapping();
        columnMapping.setNumber(columnNumber);
        columnMapping.setPartialized(true);
        
        String label = String.format("column%d", columnNumber);
        if(partialAnno.isPresent()) {
            for(CsvPartial.Header header : partialAnno.get().headers()) {
                if(header.number() == columnNumber) {
                    label = header.label();
                }
            }
        }
        columnMapping.setLabel(label);
        
        return columnMapping;
        
    }
    
    /**
     * システム情報を取得します。
     * @return 既存のシステム情報を変更する際に取得します。
     */
    public Configuration getConfiguration() {
        return configuration;
    }
    
    /**
     * システム情報を取得します。
     * @param configuraton 新しくシステム情報を変更する際に設定します。
     */
    public void setConfiguration(Configuration configuraton) {
        this.configuration = configuraton;
    }
    
}