package com.gh.mygreen.xlsmapper; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Annotation; 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.XlsPostSave; import com.gh.mygreen.xlsmapper.annotation.XlsPreSave; import com.gh.mygreen.xlsmapper.annotation.XlsSheet; import com.gh.mygreen.xlsmapper.fieldprocessor.FieldAdaptor; import com.gh.mygreen.xlsmapper.fieldprocessor.SavingFieldProcessor; 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; /** * JavaBeanをExcelのシートにマッピングし出力するクラス。 * * @version 1.5 * @author T.TSUCHIE * */ public class XlsSaver { private static final Logger logger = LoggerFactory.getLogger(XlsSaver.class); private XlsMapperConfig config; public XlsSaver(XlsMapperConfig config) { this.config = config; } public XlsSaver() { this(new XlsMapperConfig()); } /** * JavaのオブジェクトをExeclファイルに出力する。 * <p>出力するファイルは、引数で指定した雛形となるテンプレート用のExcelファイルをもとに出力する。 * @param templateXlsIn 雛形となるExcelファイルの入力 * @param xlsOut 出力 * @param beanObj 書き込むオブジェクト * @throws XlsMapperException * @throws IOException */ public void save(final InputStream templateXlsIn, final OutputStream xlsOut, final Object beanObj) throws XlsMapperException, IOException { ArgUtils.notNull(templateXlsIn, "templateXlsIn"); ArgUtils.notNull(xlsOut, "xlsOut"); ArgUtils.notNull(beanObj, "beanObj"); save(templateXlsIn, xlsOut, beanObj, null); } /** * XMLによるマッピングを指定して、JavaのオブジェクトをExcelファイルに出力する。 * @param templateXlsIn * @param xlsOut * @param beanObj * @param xmlIn * @throws XlsMapperException * @throws IOException */ public void save(final InputStream templateXlsIn, final OutputStream xlsOut, final Object beanObj, final InputStream xmlIn) throws XlsMapperException, IOException { ArgUtils.notNull(templateXlsIn, "templateXlsIn"); ArgUtils.notNull(xlsOut, "xlsOut"); ArgUtils.notNull(beanObj, "beanObj"); // Xmls情報の出力 XmlInfo xmlInfo = null; if(xmlIn != null) { xmlInfo = XmlIO.load(xmlIn); } final AnnotationReader annoReader = new AnnotationReader(xmlInfo); final SavingWorkObject work = new SavingWorkObject(); work.setAnnoReader(annoReader); work.setErrors(new SheetBindingErrors(beanObj.getClass())); final Workbook book; try { book = WorkbookFactory.create(templateXlsIn); } catch (InvalidFormatException | IOException e) { throw new XlsMapperException("fail load template Excel File", e); } final Class<?> clazz = beanObj.getClass(); 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().findForSaving(book, sheetAnno, annoReader, beanObj); saveSheet(xlsSheet[0], beanObj, work); } catch(SheetNotFoundException e) { if(config.isIgnoreSheetNotFound()){ logger.warn("skip saving by not-found sheet.", e); return; } else { throw e; } } if(config.isFormulaRecalcurationOnSave()) { book.setForceFormulaRecalculation(true); } book.write(xlsOut); } /** * 複数のオブジェクトをそれぞれのシートへ保存する。 * @param templateXlsIn 雛形となるExcelファイルの入力 * @param xlsOut 出力 * @param beanObjs 書き込むオブジェクトの配列。 * @throws XlsMapperException * @throws IOException */ public void saveMultiple(final InputStream templateXlsIn, final OutputStream xlsOut, final Object[] beanObjs) throws XlsMapperException, IOException { ArgUtils.notNull(templateXlsIn, "templateXlsIn"); ArgUtils.notNull(xlsOut, "xlsOut"); ArgUtils.notEmpty(beanObjs, "beanObjs"); saveMultiple(templateXlsIn, xlsOut, beanObjs, null); } /** * 複数のオブジェクトをそれぞれのシートへ保存する。 * @param templateXlsIn 雛形となるExcelファイルの入力 * @param xlsOut 出力 * @param beanObjs 書き込むオブジェクトの配列。 * @param xmlIn アノテーションの定義をしているXMLファイルの入力。 * @throws XlsMapperException * @throws IOException */ public void saveMultiple(final InputStream templateXlsIn, final OutputStream xlsOut, final Object[] beanObjs, final InputStream xmlIn) throws XlsMapperException, IOException { ArgUtils.notNull(templateXlsIn, "templateXlsIn"); ArgUtils.notNull(xlsOut, "xlsOut"); ArgUtils.notEmpty(beanObjs, "beanObjs"); // Xmls情報の出力 XmlInfo xmlInfo = null; if(xmlIn != null) { xmlInfo = XmlIO.load(xmlIn); } final SheetBindingErrorsContainer errorsContainer = new SheetBindingErrorsContainer(getObjectNames(beanObjs)); final AnnotationReader annoReader = new AnnotationReader(xmlInfo); final Workbook book; try { book = WorkbookFactory.create(templateXlsIn); } catch (InvalidFormatException | IOException e) { throw new XlsMapperException("fail load template Excel File", e); } for(int i=0; i < beanObjs.length; i++) { final Object beanObj = beanObjs[i]; final Class<?> clazz = beanObj.getClass(); final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class); if(sheetAnno == null) { throw new AnnotationInvalidException(String.format("With '%s', cannot finld annoation '@XlsSheet'", clazz.getName()), sheetAnno); } final SavingWorkObject work = new SavingWorkObject(); work.setAnnoReader(annoReader); try { final Sheet[] xlsSheet = config.getSheetFinder().findForSaving(book, sheetAnno, annoReader, beanObj); work.setErrors(errorsContainer.findBindingResult(i)); saveSheet(xlsSheet[0], beanObj, work); } catch(SheetNotFoundException e) { if(config.isIgnoreSheetNotFound()){ logger.warn("skip saving by not-found sheet.", e); continue; } else { throw e; } } } if(config.isFormulaRecalcurationOnSave()) { book.setForceFormulaRecalculation(true); } book.write(xlsOut); } private String[] getObjectNames(final Object[] beanObjs) { List<String> names = new ArrayList<String>(); for(Object item : beanObjs) { names.add(item.getClass().getCanonicalName()); } return names.toArray(new String[names.size()]); } /** * 任意のクラスのオブジェクトを、Excelシートにマッピングする。 * @param sheet * @param beanObj * @param config * @param work * @throws XlsMapperException */ @SuppressWarnings({"rawtypes"}) private void saveSheet(final Sheet sheet, final Object beanObj, final SavingWorkObject work) throws XlsMapperException { final Class<?> clazz = beanObj.getClass(); work.getErrors().setSheetName(sheet.getSheetName()); // リスナークラスの@PreSave用メソッドの実行 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 XlsPreSave preProcessAnno = work.getAnnoReader().getAnnotation(listenerAnno.listenerClass(), method, XlsPreSave.class); if(preProcessAnno != null) { Utils.invokeNeedProcessMethod(listenerObj, method, beanObj, sheet, config, work.getErrors()); } } } // @PreSave用のメソッドの取得と実行 for(Method method : clazz.getMethods()) { final XlsPreSave preProcessAnno = work.getAnnoReader().getAnnotation(beanObj.getClass(), method, XlsPreSave.class); if(preProcessAnno != null) { Utils.invokeNeedProcessMethod(beanObj, method, beanObj, sheet, config, work.getErrors()); } } final List<FieldAdaptorProxy> adaptorProxies = new ArrayList<>(); // public メソッドの処理 for(Method method : clazz.getMethods()) { method.setAccessible(true); for(Annotation anno : work.getAnnoReader().getAnnotations(clazz, method)) { final SavingFieldProcessor processor = config.getFieldProcessorRegistry().getSavingProcessor(anno); if((Utils.isGetterMethod(method) || Utils.isBooleanGetterMethod(method))&& processor != null) { final FieldAdaptor adaptor = new FieldAdaptor(clazz, method, work.getAnnoReader()); adaptorProxies.add(new FieldAdaptorProxy(anno, processor, adaptor)); } else if(anno instanceof XlsPostSave) { 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 SavingFieldProcessor processor = config.getFieldProcessorRegistry().getSavingProcessor(anno); if(processor != null) { adaptorProxies.add(new FieldAdaptorProxy(anno, processor, adaptor)); } } } // 順番を並び替えて保存処理を実行する Collections.sort(adaptorProxies, HintOrderComparator.createForSaving()); for(FieldAdaptorProxy adaptorProxy : adaptorProxies) { adaptorProxy.saveProcess(sheet, beanObj, config, work); } // リスナークラスの@PostSaveの取得 if(listenerAnno != null) { Object listenerObj = config.createBean(listenerAnno.listenerClass()); for(Method method : listenerObj.getClass().getMethods()) { final XlsPostSave postProcessAnno = work.getAnnoReader().getAnnotation(listenerAnno.listenerClass(), method, XlsPostSave.class); if(postProcessAnno != null) { work.addNeedPostProcess(new NeedProcess(beanObj, listenerObj, method)); } } } //@PostSaveが付与されているメソッドの実行 for(NeedProcess need : work.getNeedPostProcesses()) { Utils.invokeNeedProcessMethod(need.getProcess(), need.getMethod(), need.getTarget(), sheet, config, work.getErrors()); } } public XlsMapperConfig getConfig() { return config; } public void setConfig(XlsMapperConfig config) { this.config = config; } }