XlsLoader.java

  1. package com.gh.mygreen.xlsmapper;

  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.lang.annotation.Annotation;
  5. import java.lang.reflect.Array;
  6. import java.lang.reflect.Field;
  7. import java.lang.reflect.Method;
  8. import java.util.ArrayList;
  9. import java.util.Collections;
  10. import java.util.List;

  11. import org.apache.poi.ss.usermodel.Sheet;
  12. import org.apache.poi.ss.usermodel.Workbook;
  13. import org.apache.poi.ss.usermodel.WorkbookFactory;
  14. import org.slf4j.Logger;
  15. import org.slf4j.LoggerFactory;

  16. import com.gh.mygreen.xlsmapper.annotation.XlsFieldProcessor;
  17. import com.gh.mygreen.xlsmapper.annotation.XlsListener;
  18. import com.gh.mygreen.xlsmapper.annotation.XlsPostLoad;
  19. import com.gh.mygreen.xlsmapper.annotation.XlsPreLoad;
  20. import com.gh.mygreen.xlsmapper.annotation.XlsSheet;
  21. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessor;
  22. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorFactory;
  23. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorProxy;
  24. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorProxyComparator;
  25. import com.gh.mygreen.xlsmapper.fieldprocessor.FieldProcessor;
  26. import com.gh.mygreen.xlsmapper.fieldprocessor.ProcessCase;
  27. import com.gh.mygreen.xlsmapper.localization.MessageBuilder;
  28. import com.gh.mygreen.xlsmapper.util.ArgUtils;
  29. import com.gh.mygreen.xlsmapper.util.ClassUtils;
  30. import com.gh.mygreen.xlsmapper.util.Utils;
  31. import com.gh.mygreen.xlsmapper.validation.MultipleSheetBindingErrors;
  32. import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
  33. import com.gh.mygreen.xlsmapper.xml.AnnotationReader;


  34. /**
  35.  * ExcelのシートをJavaBeanにマッピングするクラス。
  36.  *
  37.  * @version 2.0
  38.  * @author T.TSUCHIE
  39.  *
  40.  */
  41. public class XlsLoader {

  42.     private static final Logger logger = LoggerFactory.getLogger(XlsLoader.class);

  43.     private Configuration configuration;

  44.     /**
  45.      * 独自のシステム情報を設定するコンストラクタ
  46.      * @param configuration システム情報
  47.      */
  48.     public XlsLoader(final Configuration configuration) {
  49.         this.configuration = configuration;
  50.     }

  51.     /**
  52.      * デフォルトのコンストラクタ
  53.      */
  54.     public XlsLoader() {
  55.         this(new Configuration());
  56.     }

  57.     /**
  58.      * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。
  59.      *
  60.      * @param <P> シートをマッピングするクラスタイプ
  61.      * @param xlsIn 読み込みもとのExcelファイルのストリーム。
  62.      * @param clazz マッピング先のクラスタイプ。
  63.      * @return シートをマッピングしたオブジェクト。
  64.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、nullを返します。
  65.      * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
  66.      * @throws XlsMapperException Excelファイルのマッピングに失敗した場合
  67.      * @throws IOException ファイルの読み込みに失敗した場合
  68.      *
  69.      */
  70.     public <P> P load(final InputStream xlsIn, final Class<P> clazz)  throws XlsMapperException, IOException {

  71.         ArgUtils.notNull(xlsIn, "xlsIn");
  72.         ArgUtils.notNull(clazz, "clazz");

  73.         return loadDetail(xlsIn, clazz).getTarget();
  74.     }

  75.     /**
  76.      * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。
  77.      *
  78.      * @param <P> シートをマッピングするクラスタイプ
  79.      * @param xlsIn 読み込み元のExcelファイルのストリーム。
  80.      * @param clazz マッピング先のクラスタイプ。
  81.      * @return マッピングの詳細情報。
  82.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、nullを返します。
  83.      *
  84.      * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
  85.      * @throws XlsMapperException Excelファイルのマッピングに失敗した場合
  86.      * @throws IOException ファイルの読み込みに失敗した場合
  87.      */
  88.     public <P> SheetBindingErrors<P> loadDetail(final InputStream xlsIn, final Class<P> clazz)
  89.             throws XlsMapperException, IOException {

  90.         ArgUtils.notNull(xlsIn, "xlsIn");
  91.         ArgUtils.notNull(clazz, "clazz");

  92.         final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));

  93.         final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class);
  94.         if(sheetAnno == null) {
  95.             throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
  96.                     .varWithClass("property", clazz)
  97.                     .varWithAnno("anno", XlsSheet.class)
  98.                     .format());
  99.         }

  100.         Workbook book = null;
  101.         try {
  102.             book = WorkbookFactory.create(xlsIn);

  103.         } finally {
  104.             if(book != null) {
  105.                 book.close();
  106.             }
  107.         }

  108.         try {
  109.             final Sheet[] xlsSheet = configuration.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz);
  110.             return loadSheet(xlsSheet[0], clazz, annoReader);

  111.         } catch(SheetNotFoundException e) {
  112.             if(configuration.isIgnoreSheetNotFound()){
  113.                 logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
  114.                 return null;

  115.             } else {
  116.                 throw e;
  117.             }
  118.         }
  119.     }

  120.     /**
  121.      * Excelファイルの同じ形式の複数シートを読み込み、任意のクラスにマップする。
  122.      * <p>{@link XlsSheet#regex()}により、複数のシートが同じ形式で、同じクラスにマッピングすする際に使用します。</p>
  123.      *
  124.      * @param <P> シートをマッピングするクラスタイプ
  125.      * @param xlsIn 読み込み元のExcelファイルのストリーム。
  126.      * @param clazz マッピング先のクラスタイプ。
  127.      * @return マッピングした複数のシート。
  128.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
  129.      * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
  130.      * @throws XlsMapperException マッピングに失敗した場合
  131.      * @throws IOException ファイルの読み込みに失敗した場合
  132.      */
  133.     @SuppressWarnings("unchecked")
  134.     public <P> P[] loadMultiple(final InputStream xlsIn, final Class<P> clazz) throws XlsMapperException, IOException {

  135.         return loadMultipleDetail(xlsIn, clazz).getAll().stream()
  136.                 .map(s -> s.getTarget())
  137.                 .toArray(n -> (P[])Array.newInstance(clazz, n));
  138.     }

  139.     /**
  140.      * Excelファイルの同じ形式の複数シートを読み込み、任意のクラスにマップする。
  141.      * <p>{@link XlsSheet#regex()}により、複数のシートが同じ形式で、同じクラスにマッピングすする際に使用します。</p>
  142.      *
  143.      * @param <P> シートをマッピングするクラスタイプ
  144.      * @param xlsIn 読み込み元のExcelファイルのストリーム。
  145.      * @param clazz マッピング先のクラスタイプ。
  146.      * @return 複数のシートのマッピング結果。
  147.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
  148.      * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
  149.      * @throws XlsMapperException マッピングに失敗した場合
  150.      * @throws IOException ファイルの読み込みに失敗した場合
  151.      */
  152.     public <P> MultipleSheetBindingErrors<P> loadMultipleDetail(final InputStream xlsIn, final Class<P> clazz)
  153.             throws XlsMapperException, IOException {

  154.         ArgUtils.notNull(xlsIn, "xlsIn");
  155.         ArgUtils.notNull(clazz, "clazz");

  156.         final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));

  157.         final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class);
  158.         if(sheetAnno == null) {
  159.             throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
  160.                     .varWithClass("property", clazz)
  161.                     .varWithAnno("anno", XlsSheet.class)
  162.                     .format());
  163.         }

  164.         final MultipleSheetBindingErrors<P> multipleResult = new MultipleSheetBindingErrors<>();

  165.         Workbook book = null;
  166.         try {
  167.             book = WorkbookFactory.create(xlsIn);

  168.         } finally {
  169.             if(book != null) {
  170.                 book.close();
  171.             }
  172.         }

  173.         if(sheetAnno.number() == -1 && sheetAnno.name().isEmpty() && sheetAnno.regex().isEmpty()) {
  174.             // 読み込むシートの条件が指定されていない場合、全て読み込む
  175.             int sheetNum = book.getNumberOfSheets();
  176.             for(int i=0; i < sheetNum; i++) {
  177.                 final Sheet sheet = book.getSheetAt(i);

  178.                 multipleResult.addBindingErrors(loadSheet(sheet, clazz, annoReader));

  179.             }

  180.         } else {
  181.             // 読み込むシートの条件が指定されている場合
  182.             try {
  183.                 final Sheet[] xlsSheet = configuration.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz);
  184.                 for(Sheet sheet : xlsSheet) {
  185.                     multipleResult.addBindingErrors(loadSheet(sheet, clazz, annoReader));

  186.                 }

  187.             } catch(SheetNotFoundException e) {
  188.                 if(configuration.isIgnoreSheetNotFound()){
  189.                     logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
  190.                 } else {
  191.                     throw e;
  192.                 }
  193.             }

  194.         }

  195.         return multipleResult;
  196.     }

  197.     /**
  198.      * Excelファイルの異なる形式の複数シートを読み込み、任意のクラスにマップする。
  199.      * <p>複数のシートの形式を一度に読み込む際に使用します。</p>
  200.      *
  201.      * @param xlsIn 読み込み元のExcelファイルのストリーム。
  202.      * @param classes マッピング先のクラスタイプの配列。
  203.      * @return マッピングした複数のシート。
  204.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
  205.      * @throws IllegalArgumentException {@literal xlsIn == null or classes == null}
  206.      * @throws IllegalArgumentException {@literal calsses.length == 0}
  207.      * @throws XlsMapperException マッピングに失敗した場合
  208.      * @throws IOException ファイルの読み込みに失敗した場合
  209.      */
  210.     public Object[] loadMultiple(final InputStream xlsIn, final Class<?>[] classes)
  211.             throws XlsMapperException, IOException {
  212.         return loadMultipleDetail(xlsIn, classes).getAll().stream()
  213.                 .map(s -> s.getTarget())
  214.                 .toArray();
  215.     }

  216.     /**
  217.      * Excelファイルの異なる形式の複数シートを読み込み、任意のクラスにマップする。
  218.      * <p>複数のシートの形式を一度に読み込む際に使用します。</p>
  219.      *
  220.      * @param xlsIn 読み込み元のExcelファイルのストリーム。
  221.      * @param classes マッピング先のクラスタイプの配列。
  222.      * @return マッピングした複数のシートの結果。
  223.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
  224.      * @throws IllegalArgumentException {@literal xlsIn == null or classes == null}
  225.      * @throws IllegalArgumentException {@literal calsses.length == 0}
  226.      * @throws IOException ファイルの読み込みに失敗した場合
  227.      * @throws XlsMapperException マッピングに失敗した場合
  228.      */
  229.     @SuppressWarnings({"unchecked", "rawtypes"})
  230.     public MultipleSheetBindingErrors<Object> loadMultipleDetail(final InputStream xlsIn, final Class<?>[] classes)
  231.             throws XlsMapperException, IOException {

  232.         ArgUtils.notNull(xlsIn, "xlsIn");
  233.         ArgUtils.notEmpty(classes, "classes");

  234.         final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));

  235.         final MultipleSheetBindingErrors<Object> multipleStore = new MultipleSheetBindingErrors<>();

  236.         Workbook book = null;
  237.         try {
  238.             book = WorkbookFactory.create(xlsIn);

  239.         } finally {
  240.             if(book != null) {
  241.                 book.close();
  242.             }
  243.         }

  244.         for(Class<?> clazz : classes) {
  245.             final XlsSheet sheetAnno = clazz.getAnnotation(XlsSheet.class);
  246.             if(sheetAnno == null) {
  247.                 throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
  248.                         .varWithClass("property", clazz)
  249.                         .varWithAnno("anno", XlsSheet.class)
  250.                         .format());
  251.             }

  252.             try {
  253.                 final Sheet[] xlsSheet = configuration.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz);
  254.                 for(Sheet sheet : xlsSheet) {
  255.                     multipleStore.addBindingErrors(loadSheet(sheet, (Class)clazz, annoReader));

  256.                 }

  257.             } catch(SheetNotFoundException ex){
  258.                 if(!configuration.isIgnoreSheetNotFound()){
  259.                     logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), ex);
  260.                     throw ex;
  261.                 }
  262.             }

  263.         }

  264.         return multipleStore;
  265.     }

  266.     /**
  267.      * シートを読み込み、任意のクラスにマッピングする。
  268.      * @param sheet シート情報
  269.      * @param clazz マッピング先のクラスタイプ。
  270.      * @param annoReader
  271.      * @return シートのマッピング情報
  272.      * @throws XlsMapperException
  273.      *
  274.      */
  275.     private <P> SheetBindingErrors<P> loadSheet(final Sheet sheet, final Class<P> clazz, final AnnotationReader annoReader)
  276.             throws XlsMapperException {

  277.         // 値の読み込み対象のJavaBeanオブジェクトの作成
  278.         final P beanObj = configuration.createBean(clazz);

  279.         final SheetBindingErrors<P> errors =  configuration.getBindingErrorsFactory().create(beanObj);
  280.         errors.setSheetName(sheet.getSheetName());
  281.         errors.setSheetIndex(sheet.getWorkbook().getSheetIndex(sheet));

  282.         final LoadingWorkObject work = new LoadingWorkObject();
  283.         work.setAnnoReader(annoReader);
  284.         work.setErrors(errors);

  285.         // セルのキャッシュ情報の初期化
  286.         configuration.getCellFormatter().init(configuration.isCacheCellValueOnLoad());

  287.         final FieldAccessorFactory adpterFactory = new FieldAccessorFactory(annoReader);

  288.         // リスナークラスの@PreLoad用メソッドの実行
  289.         final XlsListener listenerAnno = annoReader.getAnnotation(beanObj.getClass(), XlsListener.class);
  290.         if(listenerAnno != null) {
  291.             for(Class<?> listenerClass : listenerAnno.value()) {
  292.                 final Object listenerObj = configuration.createBean(listenerClass);

  293.                 for(Method method : listenerObj.getClass().getMethods()) {
  294.                     if(annoReader.hasAnnotation(method, XlsPreLoad.class)) {
  295.                         Utils.invokeNeedProcessMethod(listenerObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Load);
  296.                     }
  297.                 }
  298.             }

  299.         }

  300.         // @PreLoad用のメソッドの実行
  301.         for(Method method : clazz.getMethods()) {

  302.             if(annoReader.hasAnnotation(method, XlsPreLoad.class)) {
  303.                 Utils.invokeNeedProcessMethod(beanObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Load);
  304.             }
  305.         }

  306.         final List<FieldAccessorProxy> accessorProxies = new ArrayList<>();

  307.         // public メソッドの処理
  308.         for(Method method : clazz.getMethods()) {
  309.             method.setAccessible(true);
  310.             for(Annotation anno : annoReader.getAnnotations(method)) {
  311.                 final XlsFieldProcessor annoFieldProcessor = anno.annotationType().getAnnotation(XlsFieldProcessor.class);
  312.                 if(ClassUtils.isAccessorMethod(method) && annoFieldProcessor != null) {
  313.                     // 登録済みのFieldProcessorの取得
  314.                     FieldProcessor<?> processor = configuration.getFieldProcessorRegistry().getProcessor(anno.annotationType());

  315.                     // アノテーションに指定されているFieldProcessorの場合
  316.                     if(processor == null && annoFieldProcessor.value().length > 0) {
  317.                         processor = configuration.createBean(annoFieldProcessor.value()[0]);

  318.                     }

  319.                     if(processor != null) {
  320.                         final FieldAccessor accessor = adpterFactory.create(method);
  321.                         final FieldAccessorProxy accessorProxy = new FieldAccessorProxy(anno, processor, accessor);
  322.                         if(!accessorProxies.contains(accessorProxy)) {
  323.                             accessorProxies.add(accessorProxy);
  324.                         }

  325.                     } else {
  326.                         // FieldProcessorが見つからない場合
  327.                         throw new AnnotationInvalidException(anno, MessageBuilder.create("anno.XlsFieldProcessor.notResolve")
  328.                                 .varWithAnno("anno", anno.annotationType())
  329.                                 .format());
  330.                     }

  331.                 }

  332.                 if(anno instanceof XlsPostLoad) {
  333.                     work.addNeedPostProcess(new NeedProcess(beanObj, beanObj, method));
  334.                 }
  335.             }

  336.         }

  337.         // フィールドの処理
  338.         for(Field field : clazz.getDeclaredFields()) {

  339.             field.setAccessible(true);
  340.             for(Annotation anno : annoReader.getAnnotations(field)) {

  341.                 final XlsFieldProcessor annoFieldProcessor = anno.annotationType().getAnnotation(XlsFieldProcessor.class);
  342.                 if(annoFieldProcessor != null) {

  343.                     // 登録済みのFieldProcessorの取得
  344.                     FieldProcessor<?> processor = configuration.getFieldProcessorRegistry().getProcessor(anno.annotationType());

  345.                     // アノテーションに指定されているFieldProcessorの場合
  346.                     if(processor == null && annoFieldProcessor.value().length > 0) {
  347.                         processor = configuration.createBean(annoFieldProcessor.value()[0]);

  348.                     }

  349.                     if(processor != null) {
  350.                         final FieldAccessor accessor = adpterFactory.create(field);
  351.                         final FieldAccessorProxy accessorProxy = new FieldAccessorProxy(anno, processor, accessor);
  352.                         if(!accessorProxies.contains(accessorProxy)) {
  353.                             accessorProxies.add(accessorProxy);
  354.                         }

  355.                     } else {
  356.                         // FieldProcessorが見つからない場合
  357.                         throw new AnnotationInvalidException(anno, MessageBuilder.create("anno.XlsFieldProcessor.notResolve")
  358.                                 .varWithAnno("anno", anno.annotationType())
  359.                                 .format());
  360.                     }

  361.                 }


  362.             }
  363.         }

  364.         // 順番を並び替えて保存処理を実行する
  365.         Collections.sort(accessorProxies, new FieldAccessorProxyComparator());
  366.         for(FieldAccessorProxy accessorProxy : accessorProxies) {
  367.             accessorProxy.loadProcess(sheet, beanObj, configuration, work);
  368.         }

  369.         // リスナークラスの@PostLoadの取得
  370.         if(listenerAnno != null) {
  371.             for(Class<?> listenerClass : listenerAnno.value()) {
  372.                 Object listenerObj = configuration.createBean(listenerClass);
  373.                 for(Method method : listenerObj.getClass().getMethods()) {
  374.                     if(annoReader.hasAnnotation(method, XlsPostLoad.class)) {
  375.                         work.addNeedPostProcess(new NeedProcess(beanObj, listenerObj, method));
  376.                     }
  377.                 }
  378.             }

  379.         }

  380.         //@PostLoadが付与されているメソッドの実行
  381.         for(NeedProcess need : work.getNeedPostProcesses()) {
  382.             Utils.invokeNeedProcessMethod(need.getProcess(), need.getMethod(), need.getTarget(), sheet, configuration, work.getErrors(), ProcessCase.Load);
  383.         }

  384.         // セルのキャッシュ情報の初期化
  385.         configuration.getCellFormatter().init(configuration.isCacheCellValueOnLoad());

  386.         return errors;
  387.     }

  388.     /**
  389.      * システム情報を取得します。
  390.      * @return 現在のシステム情報
  391.      */
  392.     public Configuration getConfiguration() {
  393.         return configuration;
  394.     }

  395.     /**
  396.      * システム情報を設定します。
  397.      * @param configuration システム情報
  398.      */
  399.     public void setConfiguration(Configuration configuration) {
  400.         this.configuration = configuration;
  401.     }

  402. }