XlsSaver.java
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.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.XlsPostSave;
import com.gh.mygreen.xlsmapper.annotation.XlsPreSave;
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;
/**
* JavaBeanをExcelのシートにマッピングし出力するクラス。
*
* @version 2.0
* @author T.TSUCHIE
*
*/
public class XlsSaver {
private static final Logger logger = LoggerFactory.getLogger(XlsSaver.class);
private Configuration configuration;
/**
* 独自のシステム情報を設定するコンストラクタ
* @param configuration システム情報
*/
public XlsSaver(Configuration configuration) {
this.configuration = configuration;
}
/**
* デフォルトのコンストラクタ
*/
public XlsSaver() {
this(new Configuration());
}
/**
* JavaのオブジェクトをExeclファイルに出力する。
* <p>出力するファイルは、引数で指定した雛形となるテンプレート用のExcelファイルをもとに出力する。</p>
*
* @param templateXlsIn 雛形となるExcelファイルの入力
* @param xlsOut 出力先のストリーム
* @param beanObj 書き込むBeanオブジェクト
* @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObj == null}
* @throws XlsMapperException マッピングに失敗した場合
* @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
*/
public void save(final InputStream templateXlsIn, final OutputStream xlsOut, final Object beanObj)
throws XlsMapperException, IOException {
saveDetail(templateXlsIn, xlsOut, beanObj);
}
/**
* JavaのオブジェクトをExeclファイルに出力する。
* <p>出力するファイルは、引数で指定した雛形となるテンプレート用のExcelファイルをもとに出力する。</p>
*
* @param <P> マッピング対象のクラスタイプ
* @param templateXlsIn 雛形となるExcelファイルの入力
* @param xlsOut 出力先のストリーム
* @param beanObj 書き込むBeanオブジェクト
* @return マッピング結果。
* {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、nullを返します。
* @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObj == null}
* @throws XlsMapperException マッピングに失敗した場合
* @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
*/
public <P> SheetBindingErrors<P> saveDetail(final InputStream templateXlsIn, final OutputStream xlsOut, final P beanObj)
throws XlsMapperException, IOException {
ArgUtils.notNull(templateXlsIn, "templateXlsIn");
ArgUtils.notNull(xlsOut, "xlsOut");
ArgUtils.notNull(beanObj, "beanObj");
final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));
try(Workbook book = WorkbookFactory.create(templateXlsIn)) {
final Class<?> clazz = beanObj.getClass();
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());
}
final SheetBindingErrors<P> bindingResult;
try {
final Sheet[] xlsSheet = configuration.getSheetFinder().findForSaving(book, sheetAnno, annoReader, beanObj);
bindingResult = saveSheet(xlsSheet[0], beanObj, annoReader);
} catch(SheetNotFoundException e) {
if(configuration.isIgnoreSheetNotFound()){
logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
return null;
} else {
throw e;
}
}
if(configuration.isFormulaRecalcurationOnSave()) {
book.setForceFormulaRecalculation(true);
}
book.write(xlsOut);
return bindingResult;
}
}
/**
* 複数のオブジェクトをそれぞれのシートへ保存する。
* @param templateXlsIn 雛形となるExcelファイルの入力
* @param xlsOut xlsOut 出力先のストリーム
* @param beanObjs 書き込むオブジェクトの配列。
* @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObjs == null}
* @throws XlsMapperException マッピングに失敗した場合
* @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
*/
public void saveMultiple(final InputStream templateXlsIn, final OutputStream xlsOut, final Object[] beanObjs)
throws XlsMapperException, IOException {
saveMultipleDetail(templateXlsIn, xlsOut, beanObjs);
}
/**
* 複数のオブジェクトをそれぞれのシートへ保存する。
* @param templateXlsIn 雛形となるExcelファイルの入力
* @param xlsOut xlsOut 出力先のストリーム
* @param beanObjs 書き込むオブジェクトの配列。
* @return マッピング結果。
* {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、結果に含まれません。
* @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObjs == null}
* @throws XlsMapperException マッピングに失敗した場合
* @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
*/
public MultipleSheetBindingErrors<Object> saveMultipleDetail(final InputStream templateXlsIn, final OutputStream xlsOut, final Object[] beanObjs)
throws XlsMapperException, IOException {
ArgUtils.notNull(templateXlsIn, "templateXlsIn");
ArgUtils.notNull(xlsOut, "xlsOut");
ArgUtils.notEmpty(beanObjs, "beanObjs");
final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));
final MultipleSheetBindingErrors<Object> multipleResult = new MultipleSheetBindingErrors<>();
try(Workbook book = WorkbookFactory.create(templateXlsIn)) {
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(sheetAnno, MessageBuilder.create("anno.notFound")
.varWithClass("property", clazz)
.varWithAnno("anno", XlsSheet.class)
.format());
}
try {
final Sheet[] xlsSheet = configuration.getSheetFinder().findForSaving(book, sheetAnno, annoReader, beanObj);
multipleResult.addBindingErrors(saveSheet(xlsSheet[0], beanObj, annoReader));
} catch(SheetNotFoundException e) {
if(configuration.isIgnoreSheetNotFound()){
logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
continue;
} else {
throw e;
}
}
}
if(configuration.isFormulaRecalcurationOnSave()) {
book.setForceFormulaRecalculation(true);
}
book.write(xlsOut);
return multipleResult;
}
}
/**
* 任意のクラスのオブジェクトを、Excelシートにマッピングする。
* @param sheet
* @param beanObj
* @param configuration
* @param work
* @throws XlsMapperException
*/
private <P> SheetBindingErrors<P> saveSheet(final Sheet sheet, final P beanObj, final AnnotationReader annoReader)
throws XlsMapperException {
final Class<?> clazz = beanObj.getClass();
final SheetBindingErrors<P> errors = configuration.getBindingErrorsFactory().create(beanObj);
errors.setSheetName(sheet.getSheetName());
errors.setSheetIndex(sheet.getWorkbook().getSheetIndex(sheet));
final SavingWorkObject work = new SavingWorkObject();
work.setAnnoReader(annoReader);
work.setErrors(errors);
// セルのキャッシュ情報の初期化
configuration.getCellFormatter().init(false);
final FieldAccessorFactory adpterFactory = new FieldAccessorFactory(annoReader);
// リスナークラスの@PreSave用メソッドの実行
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, XlsPreSave.class)) {
Utils.invokeNeedProcessMethod(listenerObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Save);
}
}
}
}
// @PreSave用のメソッドの取得と実行
for(Method method : clazz.getMethods()) {
final XlsPreSave preProcessAnno = annoReader.getAnnotation(method, XlsPreSave.class);
if(preProcessAnno != null) {
Utils.invokeNeedProcessMethod(beanObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Save);
}
}
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 XlsPostSave) {
work.addNeedPostProcess(new NeedProcess(beanObj, beanObj, method));
}
}
}
// フィールドの処理
for(Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
final FieldAccessor accessor = adpterFactory.create(field);
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 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.saveProcess(sheet, beanObj, configuration, work);
}
// リスナークラスの@PostSaveの取得
if(listenerAnno != null) {
for(Class<?> listenerClass : listenerAnno.value()) {
final Object listenerObj = configuration.createBean(listenerClass);
for(Method method : listenerObj.getClass().getMethods()) {
if(annoReader.hasAnnotation(method, XlsPostSave.class)) {
work.addNeedPostProcess(new NeedProcess(beanObj, listenerObj, method));
}
}
}
}
//@PostSaveが付与されているメソッドの実行
for(NeedProcess need : work.getNeedPostProcesses()) {
Utils.invokeNeedProcessMethod(need.getProcess(), need.getMethod(), need.getTarget(), sheet, configuration, work.getErrors(), ProcessCase.Save);
}
return errors;
}
/**
* システム情報を取得します。
* @return 現在のシステム情報
*/
public Configuration getConfiguration() {
return configuration;
}
/**
* システム情報を設定します。
* @param configuration システム情報
*/
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
}