XlsLoader.java
- package com.gh.mygreen.xlsmapper;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.annotation.Annotation;
- import java.lang.reflect.Array;
- import java.lang.reflect.Field;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.usermodel.Workbook;
- import org.apache.poi.ss.usermodel.WorkbookFactory;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.gh.mygreen.xlsmapper.annotation.XlsFieldProcessor;
- import com.gh.mygreen.xlsmapper.annotation.XlsListener;
- import com.gh.mygreen.xlsmapper.annotation.XlsPostLoad;
- import com.gh.mygreen.xlsmapper.annotation.XlsPreLoad;
- import com.gh.mygreen.xlsmapper.annotation.XlsSheet;
- import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessor;
- import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorFactory;
- import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorProxy;
- import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorProxyComparator;
- import com.gh.mygreen.xlsmapper.fieldprocessor.FieldProcessor;
- import com.gh.mygreen.xlsmapper.fieldprocessor.ProcessCase;
- import com.gh.mygreen.xlsmapper.localization.MessageBuilder;
- import com.gh.mygreen.xlsmapper.util.ArgUtils;
- import com.gh.mygreen.xlsmapper.util.ClassUtils;
- import com.gh.mygreen.xlsmapper.util.Utils;
- import com.gh.mygreen.xlsmapper.validation.MultipleSheetBindingErrors;
- import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
- import com.gh.mygreen.xlsmapper.xml.AnnotationReader;
- /**
- * ExcelのシートをJavaBeanにマッピングするクラス。
- *
- * @version 2.0
- * @author T.TSUCHIE
- *
- */
- public class XlsLoader {
- private static final Logger logger = LoggerFactory.getLogger(XlsLoader.class);
- private Configuration configuration;
- /**
- * 独自のシステム情報を設定するコンストラクタ
- * @param configuration システム情報
- */
- public XlsLoader(final Configuration configuration) {
- this.configuration = configuration;
- }
- /**
- * デフォルトのコンストラクタ
- */
- public XlsLoader() {
- this(new Configuration());
- }
- /**
- * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。
- *
- * @param <P> シートをマッピングするクラスタイプ
- * @param xlsIn 読み込みもとのExcelファイルのストリーム。
- * @param clazz マッピング先のクラスタイプ。
- * @return シートをマッピングしたオブジェクト。
- * {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、nullを返します。
- * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
- * @throws XlsMapperException Excelファイルのマッピングに失敗した場合
- * @throws IOException ファイルの読み込みに失敗した場合
- *
- */
- public <P> P load(final InputStream xlsIn, final Class<P> clazz) throws XlsMapperException, IOException {
- ArgUtils.notNull(xlsIn, "xlsIn");
- ArgUtils.notNull(clazz, "clazz");
- return loadDetail(xlsIn, clazz).getTarget();
- }
- /**
- * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。
- *
- * @param <P> シートをマッピングするクラスタイプ
- * @param xlsIn 読み込み元のExcelファイルのストリーム。
- * @param clazz マッピング先のクラスタイプ。
- * @return マッピングの詳細情報。
- * {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、nullを返します。
- *
- * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
- * @throws XlsMapperException Excelファイルのマッピングに失敗した場合
- * @throws IOException ファイルの読み込みに失敗した場合
- */
- public <P> SheetBindingErrors<P> loadDetail(final InputStream xlsIn, final Class<P> clazz)
- throws XlsMapperException, IOException {
- ArgUtils.notNull(xlsIn, "xlsIn");
- ArgUtils.notNull(clazz, "clazz");
- final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));
- final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class);
- if(sheetAnno == null) {
- throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
- .varWithClass("property", clazz)
- .varWithAnno("anno", XlsSheet.class)
- .format());
- }
- Workbook book = null;
- try {
- book = WorkbookFactory.create(xlsIn);
- } finally {
- if(book != null) {
- book.close();
- }
- }
- try {
- final Sheet[] xlsSheet = configuration.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz);
- return loadSheet(xlsSheet[0], clazz, annoReader);
- } catch(SheetNotFoundException e) {
- if(configuration.isIgnoreSheetNotFound()){
- logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
- return null;
- } else {
- throw e;
- }
- }
- }
- /**
- * Excelファイルの同じ形式の複数シートを読み込み、任意のクラスにマップする。
- * <p>{@link XlsSheet#regex()}により、複数のシートが同じ形式で、同じクラスにマッピングすする際に使用します。</p>
- *
- * @param <P> シートをマッピングするクラスタイプ
- * @param xlsIn 読み込み元のExcelファイルのストリーム。
- * @param clazz マッピング先のクラスタイプ。
- * @return マッピングした複数のシート。
- * {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
- * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
- * @throws XlsMapperException マッピングに失敗した場合
- * @throws IOException ファイルの読み込みに失敗した場合
- */
- @SuppressWarnings("unchecked")
- public <P> P[] loadMultiple(final InputStream xlsIn, final Class<P> clazz) throws XlsMapperException, IOException {
- return loadMultipleDetail(xlsIn, clazz).getAll().stream()
- .map(s -> s.getTarget())
- .toArray(n -> (P[])Array.newInstance(clazz, n));
- }
- /**
- * Excelファイルの同じ形式の複数シートを読み込み、任意のクラスにマップする。
- * <p>{@link XlsSheet#regex()}により、複数のシートが同じ形式で、同じクラスにマッピングすする際に使用します。</p>
- *
- * @param <P> シートをマッピングするクラスタイプ
- * @param xlsIn 読み込み元のExcelファイルのストリーム。
- * @param clazz マッピング先のクラスタイプ。
- * @return 複数のシートのマッピング結果。
- * {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
- * @throws IllegalArgumentException {@literal xlsIn == null or clazz == null}
- * @throws XlsMapperException マッピングに失敗した場合
- * @throws IOException ファイルの読み込みに失敗した場合
- */
- public <P> MultipleSheetBindingErrors<P> loadMultipleDetail(final InputStream xlsIn, final Class<P> clazz)
- throws XlsMapperException, IOException {
- ArgUtils.notNull(xlsIn, "xlsIn");
- ArgUtils.notNull(clazz, "clazz");
- final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));
- final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class);
- if(sheetAnno == null) {
- throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
- .varWithClass("property", clazz)
- .varWithAnno("anno", XlsSheet.class)
- .format());
- }
- final MultipleSheetBindingErrors<P> multipleResult = new MultipleSheetBindingErrors<>();
- Workbook book = null;
- try {
- book = WorkbookFactory.create(xlsIn);
- } finally {
- if(book != null) {
- book.close();
- }
- }
- if(sheetAnno.number() == -1 && sheetAnno.name().isEmpty() && sheetAnno.regex().isEmpty()) {
- // 読み込むシートの条件が指定されていない場合、全て読み込む
- int sheetNum = book.getNumberOfSheets();
- for(int i=0; i < sheetNum; i++) {
- final Sheet sheet = book.getSheetAt(i);
- multipleResult.addBindingErrors(loadSheet(sheet, clazz, annoReader));
- }
- } else {
- // 読み込むシートの条件が指定されている場合
- try {
- final Sheet[] xlsSheet = configuration.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz);
- for(Sheet sheet : xlsSheet) {
- multipleResult.addBindingErrors(loadSheet(sheet, clazz, annoReader));
- }
- } catch(SheetNotFoundException e) {
- if(configuration.isIgnoreSheetNotFound()){
- logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
- } else {
- throw e;
- }
- }
- }
- return multipleResult;
- }
- /**
- * Excelファイルの異なる形式の複数シートを読み込み、任意のクラスにマップする。
- * <p>複数のシートの形式を一度に読み込む際に使用します。</p>
- *
- * @param xlsIn 読み込み元のExcelファイルのストリーム。
- * @param classes マッピング先のクラスタイプの配列。
- * @return マッピングした複数のシート。
- * {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
- * @throws IllegalArgumentException {@literal xlsIn == null or classes == null}
- * @throws IllegalArgumentException {@literal calsses.length == 0}
- * @throws XlsMapperException マッピングに失敗した場合
- * @throws IOException ファイルの読み込みに失敗した場合
- */
- public Object[] loadMultiple(final InputStream xlsIn, final Class<?>[] classes)
- throws XlsMapperException, IOException {
- return loadMultipleDetail(xlsIn, classes).getAll().stream()
- .map(s -> s.getTarget())
- .toArray();
- }
- /**
- * Excelファイルの異なる形式の複数シートを読み込み、任意のクラスにマップする。
- * <p>複数のシートの形式を一度に読み込む際に使用します。</p>
- *
- * @param xlsIn 読み込み元のExcelファイルのストリーム。
- * @param classes マッピング先のクラスタイプの配列。
- * @return マッピングした複数のシートの結果。
- * {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、マッピング結果には含まれません。
- * @throws IllegalArgumentException {@literal xlsIn == null or classes == null}
- * @throws IllegalArgumentException {@literal calsses.length == 0}
- * @throws IOException ファイルの読み込みに失敗した場合
- * @throws XlsMapperException マッピングに失敗した場合
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- public MultipleSheetBindingErrors<Object> loadMultipleDetail(final InputStream xlsIn, final Class<?>[] classes)
- throws XlsMapperException, IOException {
- ArgUtils.notNull(xlsIn, "xlsIn");
- ArgUtils.notEmpty(classes, "classes");
- final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));
- final MultipleSheetBindingErrors<Object> multipleStore = new MultipleSheetBindingErrors<>();
- Workbook book = null;
- try {
- book = WorkbookFactory.create(xlsIn);
- } finally {
- if(book != null) {
- book.close();
- }
- }
- for(Class<?> clazz : classes) {
- final XlsSheet sheetAnno = clazz.getAnnotation(XlsSheet.class);
- if(sheetAnno == null) {
- throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
- .varWithClass("property", clazz)
- .varWithAnno("anno", XlsSheet.class)
- .format());
- }
- try {
- final Sheet[] xlsSheet = configuration.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz);
- for(Sheet sheet : xlsSheet) {
- multipleStore.addBindingErrors(loadSheet(sheet, (Class)clazz, annoReader));
- }
- } catch(SheetNotFoundException ex){
- if(!configuration.isIgnoreSheetNotFound()){
- logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), ex);
- throw ex;
- }
- }
- }
- return multipleStore;
- }
- /**
- * シートを読み込み、任意のクラスにマッピングする。
- * @param sheet シート情報
- * @param clazz マッピング先のクラスタイプ。
- * @param annoReader
- * @return シートのマッピング情報
- * @throws XlsMapperException
- *
- */
- private <P> SheetBindingErrors<P> loadSheet(final Sheet sheet, final Class<P> clazz, final AnnotationReader annoReader)
- throws XlsMapperException {
- // 値の読み込み対象のJavaBeanオブジェクトの作成
- final P beanObj = configuration.createBean(clazz);
- final SheetBindingErrors<P> errors = configuration.getBindingErrorsFactory().create(beanObj);
- errors.setSheetName(sheet.getSheetName());
- errors.setSheetIndex(sheet.getWorkbook().getSheetIndex(sheet));
- final LoadingWorkObject work = new LoadingWorkObject();
- work.setAnnoReader(annoReader);
- work.setErrors(errors);
- // セルのキャッシュ情報の初期化
- configuration.getCellFormatter().init(configuration.isCacheCellValueOnLoad());
- final FieldAccessorFactory adpterFactory = new FieldAccessorFactory(annoReader);
- // リスナークラスの@PreLoad用メソッドの実行
- final XlsListener listenerAnno = annoReader.getAnnotation(beanObj.getClass(), XlsListener.class);
- if(listenerAnno != null) {
- for(Class<?> listenerClass : listenerAnno.value()) {
- final Object listenerObj = configuration.createBean(listenerClass);
- for(Method method : listenerObj.getClass().getMethods()) {
- if(annoReader.hasAnnotation(method, XlsPreLoad.class)) {
- Utils.invokeNeedProcessMethod(listenerObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Load);
- }
- }
- }
- }
- // @PreLoad用のメソッドの実行
- for(Method method : clazz.getMethods()) {
- if(annoReader.hasAnnotation(method, XlsPreLoad.class)) {
- Utils.invokeNeedProcessMethod(beanObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Load);
- }
- }
- final List<FieldAccessorProxy> accessorProxies = new ArrayList<>();
- // public メソッドの処理
- for(Method method : clazz.getMethods()) {
- method.setAccessible(true);
- for(Annotation anno : annoReader.getAnnotations(method)) {
- final XlsFieldProcessor annoFieldProcessor = anno.annotationType().getAnnotation(XlsFieldProcessor.class);
- if(ClassUtils.isAccessorMethod(method) && annoFieldProcessor != null) {
- // 登録済みのFieldProcessorの取得
- FieldProcessor<?> processor = configuration.getFieldProcessorRegistry().getProcessor(anno.annotationType());
- // アノテーションに指定されているFieldProcessorの場合
- if(processor == null && annoFieldProcessor.value().length > 0) {
- processor = configuration.createBean(annoFieldProcessor.value()[0]);
- }
- if(processor != null) {
- final FieldAccessor accessor = adpterFactory.create(method);
- final FieldAccessorProxy accessorProxy = new FieldAccessorProxy(anno, processor, accessor);
- if(!accessorProxies.contains(accessorProxy)) {
- accessorProxies.add(accessorProxy);
- }
- } else {
- // FieldProcessorが見つからない場合
- throw new AnnotationInvalidException(anno, MessageBuilder.create("anno.XlsFieldProcessor.notResolve")
- .varWithAnno("anno", anno.annotationType())
- .format());
- }
- }
- if(anno instanceof XlsPostLoad) {
- work.addNeedPostProcess(new NeedProcess(beanObj, beanObj, method));
- }
- }
- }
- // フィールドの処理
- for(Field field : clazz.getDeclaredFields()) {
- field.setAccessible(true);
- for(Annotation anno : annoReader.getAnnotations(field)) {
- final XlsFieldProcessor annoFieldProcessor = anno.annotationType().getAnnotation(XlsFieldProcessor.class);
- if(annoFieldProcessor != null) {
- // 登録済みのFieldProcessorの取得
- FieldProcessor<?> processor = configuration.getFieldProcessorRegistry().getProcessor(anno.annotationType());
- // アノテーションに指定されているFieldProcessorの場合
- if(processor == null && annoFieldProcessor.value().length > 0) {
- processor = configuration.createBean(annoFieldProcessor.value()[0]);
- }
- if(processor != null) {
- final FieldAccessor accessor = adpterFactory.create(field);
- final FieldAccessorProxy accessorProxy = new FieldAccessorProxy(anno, processor, accessor);
- if(!accessorProxies.contains(accessorProxy)) {
- accessorProxies.add(accessorProxy);
- }
- } else {
- // FieldProcessorが見つからない場合
- throw new AnnotationInvalidException(anno, MessageBuilder.create("anno.XlsFieldProcessor.notResolve")
- .varWithAnno("anno", anno.annotationType())
- .format());
- }
- }
- }
- }
- // 順番を並び替えて保存処理を実行する
- Collections.sort(accessorProxies, new FieldAccessorProxyComparator());
- for(FieldAccessorProxy accessorProxy : accessorProxies) {
- accessorProxy.loadProcess(sheet, beanObj, configuration, work);
- }
- // リスナークラスの@PostLoadの取得
- if(listenerAnno != null) {
- for(Class<?> listenerClass : listenerAnno.value()) {
- Object listenerObj = configuration.createBean(listenerClass);
- for(Method method : listenerObj.getClass().getMethods()) {
- if(annoReader.hasAnnotation(method, XlsPostLoad.class)) {
- work.addNeedPostProcess(new NeedProcess(beanObj, listenerObj, method));
- }
- }
- }
- }
- //@PostLoadが付与されているメソッドの実行
- for(NeedProcess need : work.getNeedPostProcesses()) {
- Utils.invokeNeedProcessMethod(need.getProcess(), need.getMethod(), need.getTarget(), sheet, configuration, work.getErrors(), ProcessCase.Load);
- }
- // セルのキャッシュ情報の初期化
- configuration.getCellFormatter().init(configuration.isCacheCellValueOnLoad());
- return errors;
- }
- /**
- * システム情報を取得します。
- * @return 現在のシステム情報
- */
- public Configuration getConfiguration() {
- return configuration;
- }
- /**
- * システム情報を設定します。
- * @param configuration システム情報
- */
- public void setConfiguration(Configuration configuration) {
- this.configuration = configuration;
- }
- }