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.openxml4j.exceptions.InvalidFormatException; 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.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.fieldprocessor.FieldAdaptor; import com.gh.mygreen.xlsmapper.fieldprocessor.LoadingFieldProcessor; import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors; import com.gh.mygreen.xlsmapper.xml.AnnotationReader; import com.gh.mygreen.xlsmapper.xml.XmlIO; import com.gh.mygreen.xlsmapper.xml.bind.XmlInfo; /** * ExcelのシートをJavaBeanにマッピングするクラス。 * * @version 1.4.4 * @author T.TSUCHIE * */ public class XlsLoader { private static final Logger logger = LoggerFactory.getLogger(XlsLoader.class); private XlsMapperConfig config; public XlsLoader(final XlsMapperConfig config) { this.config = config; } public XlsLoader() { this(new XlsMapperConfig()); } /** * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。 * @param xlsIn 読み込みもとのExcelファイルのストリーム。 * @param clazz マッピング先のクラスタイプ。 * @return * @throws XlsMapperException * @throws IOException * @throws IllegalArgumentException xlsIn == null. * @throws IllegalArgumentException clazz == null. * */ public <P> P load(final InputStream xlsIn, final Class<P> clazz) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); return load(xlsIn, clazz, null, null); } /** * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。 * @param xlsIn 読み込みもとのExcelファイルのストリーム。 * @param clazz マッピング先のクラスタイプ。 * @param xmlIn アノテーションの定義をしているXMLファイルの入力。指定しない場合は、nullを指定する。 * @return * @throws XlsMapperException * @throws IOException * @throws IllegalArgumentException xlsIn == null. * @throws IllegalArgumentException clazz == null. */ public <P> P load(final InputStream xlsIn, final Class<P> clazz, final InputStream xmlIn) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); return load(xlsIn, clazz, xmlIn, null); } /** * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。 * @param xlsIn 読み込みもとのExcelファイルのストリーム。 * @param clazz マッピング先のクラスタイプ。 * @param errors エラー内容 * @return * @throws XlsMapperException * @throws IOException * @throws IllegalArgumentException xlsIn == null. * @throws IllegalArgumentException clazz == null. * */ public <P> P load(final InputStream xlsIn, final Class<P> clazz, final SheetBindingErrors errors) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); return load(xlsIn, clazz, null, errors); } /** * Excelファイルの1シートを読み込み、任意のクラスにマッピングする。 * @param xlsIn 読み込みもとのExcelファイルのストリーム。 * @param clazz マッピング先のクラスタイプ。 * @param xmlIn アノテーションの定義をしているXMLファイルの入力。指定しない場合は、nullを指定する。 * @param errors マッピング時のエラー情報。指定しない場合は、nulを指定する。 * @return * @throws XlsMapperException * @throws IOException * @throws IllegalArgumentException xlsIn == null. * @throws IllegalArgumentException clazz == null. */ public <P> P load(final InputStream xlsIn, final Class<P> clazz, final InputStream xmlIn, final SheetBindingErrors errors) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); XmlInfo xmlInfo = null; if(xmlIn != null) { xmlInfo = XmlIO.load(xmlIn); } final LoadingWorkObject work = new LoadingWorkObject(); final AnnotationReader annoReader = new AnnotationReader(xmlInfo); work.setAnnoReader(annoReader); if(errors != null) { work.setErrors(errors); } else { work.setErrors(new SheetBindingErrors(clazz)); } final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class); if(sheetAnno == null) { throw new AnnotationInvalidException(String.format("With '%s', cannot find annoation '@XlsSheet'", clazz.getName()), sheetAnno); } final Workbook book; try { book = WorkbookFactory.create(xlsIn); } catch (InvalidFormatException e) { throw new XlsMapperException("fail load Excel File", e); } try { final Sheet[] xlsSheet = config.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz); return loadSheet(xlsSheet[0], clazz, work); } catch(SheetNotFoundException e) { if(config.isIgnoreSheetNotFound()){ logger.warn("skip loading by not-found sheet.", e); return null; } else { throw e; } } } /** * Excelファイルの複数シートを読み込み、任意のクラスにマップする。 * @param xlsIn * @param clazz * @return * @throws XlsMapperException * @throws IOException */ public <P> P[] loadMultiple(final InputStream xlsIn, final Class<P> clazz) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); return loadMultiple(xlsIn, clazz, null, null); } /** * Excelファイルの複数シートを読み込み、任意のクラスにマップする。 * @param xlsIn * @param clazz * @param xmlIn * @return * @throws XlsMapperException * @throws IOException */ public <P> P[] loadMultiple(final InputStream xlsIn, final Class<P> clazz, final InputStream xmlIn) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); return loadMultiple(xlsIn, clazz, xmlIn, null); } /** * Excelファイルの複数シートを読み込み、任意のクラスにマップする。 * @param xlsIn * @param clazz * @param errorsContainer * @return * @throws XlsMapperException * @throws IOException */ public <P> P[] loadMultiple(final InputStream xlsIn, final Class<P> clazz, final SheetBindingErrorsContainer errorsContainer) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); return loadMultiple(xlsIn, clazz, null, errorsContainer); } /** * XMLによるマッピングを指定し、Excelファイルの複数シートを読み込み、任意のクラスにマップする。 * @param xlsIn * @param clazz * @param xmlIn * @param errorsContainer * @return * @throws XlsMapperException * @throws IOException */ @SuppressWarnings("unchecked") public <P> P[] loadMultiple(final InputStream xlsIn, final Class<P> clazz, final InputStream xmlIn, final SheetBindingErrorsContainer errorsContainer) throws XlsMapperException, IOException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notNull(clazz, "clazz"); XmlInfo xmlInfo = null; if(xmlIn != null) { xmlInfo = XmlIO.load(xmlIn); } final AnnotationReader annoReader = new AnnotationReader(xmlInfo); final XlsSheet sheetAnno = clazz.getAnnotation(XlsSheet.class); if(sheetAnno == null) { throw new AnnotationInvalidException(String.format("With '%s', cannot finld annoation '@XlsSheet'.", clazz.getName()), sheetAnno); } final SheetBindingErrorsContainer container; if(errorsContainer != null) { container = errorsContainer; } else { container = new SheetBindingErrorsContainer(clazz); } final Workbook book; try { book = WorkbookFactory.create(xlsIn); } catch (InvalidFormatException e) { throw new XlsMapperException("fail load Excel File", e); } final List<P> list = new ArrayList<P>(); 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); final LoadingWorkObject work = new LoadingWorkObject(); work.setAnnoReader(annoReader); work.setErrors(container.findBindingResult(i)); list.add(loadSheet(sheet, clazz, work)); } } else { // 読み込むシートの条件が指定されている場合 try { final Sheet[] xlsSheet = config.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz); for(Sheet sheet : xlsSheet) { final LoadingWorkObject work = new LoadingWorkObject(); work.setAnnoReader(annoReader); work.setErrors(container.findBindingResult(list.size())); list.add(loadSheet(sheet, clazz, work)); } } catch(SheetNotFoundException e) { if(config.isIgnoreSheetNotFound()){ logger.warn("skip loading by not-found sheet.", e); } else { throw e; } } } return list.toArray((P[])Array.newInstance(clazz, list.size())); } public Object[] loadMultiple(final InputStream xlsIn, final Class<?>[] classes) throws XlsMapperException { return loadMultiple(xlsIn, classes, null, null); } public Object[] loadMultiple(final InputStream xlsIn, final Class<?>[] classes, final InputStream xmlIn) throws XlsMapperException { return loadMultiple(xlsIn, classes, xmlIn, null); } public Object[] loadMultiple(final InputStream xlsIn, final Class<?>[] classes, final SheetBindingErrorsContainer errorsContainer) throws XlsMapperException { return loadMultiple(xlsIn, classes, null, errorsContainer); } public Object[] loadMultiple(final InputStream xlsIn, final Class<?>[] classes, final InputStream xmlIn, SheetBindingErrorsContainer errorsContainer) throws XlsMapperException { ArgUtils.notNull(xlsIn, "xlsIn"); ArgUtils.notEmpty(classes, "clazz"); XmlInfo xmlInfo = null; if(xmlIn != null) { xmlInfo = XmlIO.load(xmlIn); } final AnnotationReader annoReader = new AnnotationReader(xmlInfo); final SheetBindingErrorsContainer container; if(errorsContainer != null) { container = errorsContainer; } else { container = new SheetBindingErrorsContainer(classes); } final Workbook book; try { book = WorkbookFactory.create(xlsIn); } catch (InvalidFormatException | IOException e) { throw new XlsMapperException("fail load Excel File", e); } final List<Object> list = new ArrayList<Object>(); for(Class<?> clazz : classes) { final XlsSheet sheetAnno = clazz.getAnnotation(XlsSheet.class); if(sheetAnno == null) { throw new AnnotationInvalidException(String.format("With '%s', cannot finld annoation '@XlsSheet'.", clazz.getName()), sheetAnno); } try { final Sheet[] xlsSheet = config.getSheetFinder().findForLoading(book, sheetAnno, annoReader, clazz); for(Sheet sheet : xlsSheet) { final LoadingWorkObject work = new LoadingWorkObject(); work.setAnnoReader(annoReader); work.setErrors(container.findBindingResult(list.size())); list.add(loadSheet(sheet, clazz, work)); } } catch(SheetNotFoundException ex){ if(!config.isIgnoreSheetNotFound()){ logger.warn("skip loading by not-found sheet.", ex); throw ex; } } } return list.toArray(); } /** * シートを読み込み、任意のクラスにマッピングする。 * @param sheet シート情報 * @param clazz マッピング先のクラスタイプ。 * @param work * @return * @throws Exception * */ @SuppressWarnings({"rawtypes"}) private <P> P loadSheet(final Sheet sheet, final Class<P> clazz, final LoadingWorkObject work) throws XlsMapperException { // 値の読み込み対象のJavaBeanオブジェクトの作成 final P beanObj = config.createBean(clazz); work.getErrors().setSheetName(sheet.getSheetName()); final List<FieldAdaptorProxy> adaptorProxies = new ArrayList<>(); // リスナークラスの@PreLoadd用メソッドの実行 final XlsListener listenerAnno = work.getAnnoReader().getAnnotation(beanObj.getClass(), XlsListener.class); if(listenerAnno != null) { Object listenerObj = config.createBean(listenerAnno.listenerClass()); for(Method method : listenerObj.getClass().getMethods()) { final XlsPreLoad preProcessAnno = work.getAnnoReader().getAnnotation(listenerAnno.listenerClass(), method, XlsPreLoad.class); if(preProcessAnno != null) { Utils.invokeNeedProcessMethod(listenerObj, method, beanObj, sheet, config, work.getErrors()); } } } // @PreLoad用のメソッドの実行 for(Method method : clazz.getMethods()) { final XlsPreLoad preProcessAnno = work.getAnnoReader().getAnnotation(beanObj.getClass(), method, XlsPreLoad.class); if(preProcessAnno != null) { Utils.invokeNeedProcessMethod(beanObj, method, beanObj, sheet, config, work.getErrors()); } } // public メソッドの処理 for(Method method : clazz.getMethods()) { method.setAccessible(true); for(Annotation anno : work.getAnnoReader().getAnnotations(clazz, method)) { final LoadingFieldProcessor processor = config.getFieldProcessorRegistry().getLoadingProcessor(anno); if(Utils.isSetterMethod(method) && processor != null) { final FieldAdaptor adaptor = new FieldAdaptor(clazz, method, work.getAnnoReader()); adaptorProxies.add(new FieldAdaptorProxy(anno, processor, adaptor)); } else if(anno instanceof XlsPostLoad) { work.addNeedPostProcess(new NeedProcess(beanObj, beanObj, method)); } } } // public / private / protected / default フィールドの処理 for(Field field : clazz.getDeclaredFields()) { field.setAccessible(true); final FieldAdaptor adaptor = new FieldAdaptor(clazz, field, work.getAnnoReader()); // メソッドを重複している場合は排除する。 if(adaptorProxies.contains(adaptor)) { continue; } for(Annotation anno : work.getAnnoReader().getAnnotations(clazz, field)) { final LoadingFieldProcessor processor = config.getFieldProcessorRegistry().getLoadingProcessor(anno); if(processor != null) { adaptorProxies.add(new FieldAdaptorProxy(anno, processor, adaptor)); } } } // 順番を並び替えて保存処理を実行する Collections.sort(adaptorProxies, HintOrderComparator.createForLoading()); for(FieldAdaptorProxy adaptorProxy : adaptorProxies) { adaptorProxy.loadProcess(sheet, beanObj, config, work); } // リスナークラスの@PostLoadの取得 if(listenerAnno != null) { Object listenerObj = config.createBean(listenerAnno.listenerClass()); for(Method method : listenerObj.getClass().getMethods()) { final XlsPostLoad postProcessAnno = work.getAnnoReader().getAnnotation(listenerAnno.listenerClass(), method, XlsPostLoad.class); if(postProcessAnno != null) { work.addNeedPostProcess(new NeedProcess(beanObj, listenerObj, method)); } } } //@PostLoadが付与されているメソッドの実行 for(NeedProcess need : work.getNeedPostProcesses()) { Utils.invokeNeedProcessMethod(need.getProcess(), need.getMethod(), need.getTarget(), sheet, config, work.getErrors()); } return beanObj; } public XlsMapperConfig getConfig() { return config; } public void setConfig(XlsMapperConfig config) { this.config = config; } }