SheetFinder.java

package com.gh.mygreen.xlsmapper;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import com.gh.mygreen.xlsmapper.annotation.XlsSheetName;
import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessor;
import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorFactory;
import com.gh.mygreen.xlsmapper.localization.MessageBuilder;
import com.gh.mygreen.xlsmapper.annotation.XlsSheet;
import com.gh.mygreen.xlsmapper.util.ClassUtils;
import com.gh.mygreen.xlsmapper.util.Utils;
import com.gh.mygreen.xlsmapper.xml.AnnotationReadException;
import com.gh.mygreen.xlsmapper.xml.AnnotationReader;

/**
 * 読み込み時/書き込み処理時のシートを取得するクラス。
 * <p>アノテーション{@link XlsSheet}の設定値に従いシートを取得する。
 * 
 * @since 1.1
 * @author T.TSUCHIE
 *
 */
public class SheetFinder {
    
    /**
     * 読み込み時のシートを取得する。
     * 
     * @param workbook Excelのワークブック。
     * @param sheetAnno JavaBeanのクラスに付与されているアノテーション{@link XlsSheet}。
     * @param annoReader
     * @param beanClass JavaBeanのクラス。
     * @return Excelのシート情報。複数ヒットする場合は、該当するものを全て返す。
     * @throws SheetNotFoundException 該当のシートが見つからない場合にスローする。
     * @throws AnnotationInvalidException アノテーションの使用方法が不正な場合
     * @throws AnnotationReadException アノテーションをXMLで指定する方法が不正な場合。
     */
    public Sheet[] findForLoading(final Workbook workbook, final XlsSheet sheetAnno,
            final AnnotationReader annoReader, final Class<?> beanClass)
                    throws SheetNotFoundException, AnnotationInvalidException, AnnotationReadException {
        
        if(sheetAnno.name().length() > 0) {
            // シート名から取得する。
            final Sheet xlsSheet = workbook.getSheet(sheetAnno.name());
            if(xlsSheet == null) {
                throw new SheetNotFoundException(sheetAnno.name());
            }
            return new Sheet[]{ xlsSheet };
            
        } else if(sheetAnno.number() >= 0) {
            // シート番号から取得する
            if(sheetAnno.number() >= workbook.getNumberOfSheets()) {
                throw new SheetNotFoundException(sheetAnno.number(), workbook.getNumberOfSheets());
            }
            
            return new Sheet[]{ workbook.getSheetAt(sheetAnno.number()) }; 
            
        } else if(sheetAnno.regex().length() > 0) {
            // シート名(正規表現)をもとにして、取得する。
            final Pattern pattern = Pattern.compile(sheetAnno.regex());
            final List<Sheet> matches = new ArrayList<>();
            for(int i=0; i < workbook.getNumberOfSheets(); i++) {
                final Sheet xlsSheet = workbook.getSheetAt(i);
                if(pattern.matcher(xlsSheet.getSheetName()).matches()) {
                    matches.add(xlsSheet);
                }
            }
            
            if(matches.isEmpty()) {
                throw new SheetNotFoundException(sheetAnno.regex());
            }
            
            return matches.toArray(new Sheet[matches.size()]);
        }
        
        throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.attr.required.any")
                .varWithClass("property", beanClass)
                .varWithAnno("anno", XlsSheet.class)
                .varWithArrays("attrNames", "name", "number", "regex")
                .format());
    }
    
    /**
     * 書き込み時のシートを取得する。
     * @param workbook Excelのワークブック。
     * @param sheetAnno JavaBeanのクラスに付与されているアノテーション{@link XlsSheet}。
     * @param annoReader
     * @param beanObj JavaBeanのオブジェクト。
     * @return Excelのシート情報。複数ヒットする場合は、該当するものを全て返す。
     * @throws SheetNotFoundException 該当のシートが見つからない場合にスローする。
     * @throws AnnotationInvalidException アノテーションの使用方法が不正な場合
     * @throws AnnotationReadException アノテーションをXMLで指定する方法が不正な場合。
     */
    public Sheet[] findForSaving(final Workbook workbook, final XlsSheet sheetAnno,
            final AnnotationReader annoReader, final Object beanObj)
                    throws SheetNotFoundException, AnnotationInvalidException, AnnotationReadException {
        
        if(sheetAnno.name().length() > 0) {
            // シート名から取得する。
            final Sheet xlsSheet = workbook.getSheet(sheetAnno.name());
            if(xlsSheet == null) {
                throw new SheetNotFoundException(sheetAnno.name());
            }
            return new Sheet[]{ xlsSheet };
            
        } else if(sheetAnno.number() >= 0) {
            // シート番号から取得する
            if(sheetAnno.number() >= workbook.getNumberOfSheets()) {
                throw new SheetNotFoundException(sheetAnno.number(), workbook.getNumberOfSheets());
            }
            
            return new Sheet[]{ workbook.getSheetAt(sheetAnno.number()) };
            
        } else if(sheetAnno.regex().length() > 0) {
            // シート名(正規表現)をもとにして、取得する。
            String sheetNameValue = null;
            Optional<FieldAccessor> sheetNameField = getSheetNameField(beanObj, annoReader);
            if(sheetNameField.isPresent()) {
                sheetNameValue = (String)sheetNameField.get().getValue(beanObj);
            }
            
            final Pattern pattern = Pattern.compile(sheetAnno.regex());
            final List<Sheet> matches = new ArrayList<>();
            for(int i=0; i < workbook.getNumberOfSheets(); i++) {
                final Sheet xlsSheet = workbook.getSheetAt(i);
                if(pattern.matcher(xlsSheet.getSheetName()).matches()) {
                    
                    // オブジェクト中の@XslSheetNameで値が設定されている場合、Excelファイル中の一致するシートを元にする比較する
                    if(Utils.isNotEmpty(sheetNameValue) && xlsSheet.getSheetName().equals(sheetNameValue)) {
                        return new Sheet[]{ xlsSheet };
                        
                    }
                    
                    matches.add(xlsSheet);
                }
            }
            
            if(sheetNameValue != null && !matches.isEmpty()) {
                // シート名が直接指定の場合
                throw new SheetNotFoundException(sheetNameValue);
                
            } else if(matches.isEmpty()) {
                throw new SheetNotFoundException(sheetAnno.regex());
                
            } else if(matches.size() == 1) {
                // 1つのシートに絞り込めた場合
                return new Sheet[]{ matches.get(0) };
                
            } else {
                // 複数のシートがヒットした場合
                List<String> names = new ArrayList<>();
                for(Sheet sheet : matches) {
                    names.add(sheet.getSheetName());
                }
                throw new SheetNotFoundException(sheetAnno.regex(),
                        MessageBuilder.create("sheet.regexMultipleHit")
                            .var("regex", sheetAnno.regex())
                            .var("names", names)
                            .format());
            }
        }
        
        throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.attr.required.any")
                .varWithClass("property", beanObj.getClass())
                .varWithAnno("anno", XlsSheet.class)
                .varWithArrays("attrNames", "name", "number", "regex")
                .format());
        
    }
    
    /**
     * アノテーション「@XlsSheetName」が付与されているフィールド/メソッドを取得する。
     * @param beanObj
     * @param config
     * @param annoReader
     * @return 見つからない場合、空を返す。
     * @throws AnnotationReadException 
     */
    private Optional<FieldAccessor> getSheetNameField(final Object beanObj, final AnnotationReader annoReader) throws AnnotationReadException {
        
        FieldAccessorFactory adpterFactory = new FieldAccessorFactory(annoReader);
        
        Class<?> clazz = beanObj.getClass();
        for(Method method : clazz.getMethods()) {
            method.setAccessible(true);
            if(!ClassUtils.isGetterMethod(method)) {
                continue;
            }
            
            if(!annoReader.hasAnnotation(method, XlsSheetName.class)) {
                continue;
            }
            
            return Optional.of(adpterFactory.create(method));
        }
        
        for(Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            
            if(!annoReader.hasAnnotation(field, XlsSheetName.class)) {
                continue;
            }
            
            return Optional.of(adpterFactory.create(field));
            
        }
        
        // not found
        return Optional.empty();
    }
    
}