package com.gh.mygreen.xlsmapper.cellconvert.converter;
import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Currency;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import com.gh.mygreen.xlsmapper.POIUtils;
import com.gh.mygreen.xlsmapper.Utils;
import com.gh.mygreen.xlsmapper.XlsMapperConfig;
import com.gh.mygreen.xlsmapper.XlsMapperException;
import com.gh.mygreen.xlsmapper.annotation.XlsConverter;
import com.gh.mygreen.xlsmapper.annotation.XlsFormula;
import com.gh.mygreen.xlsmapper.annotation.XlsNumberConverter;
import com.gh.mygreen.xlsmapper.cellconvert.AbstractCellConverter;
import com.gh.mygreen.xlsmapper.cellconvert.TypeBindException;
import com.gh.mygreen.xlsmapper.fieldprocessor.FieldAdaptor;
/**
* 数値型のConverterの抽象クラス。
* <p>数値型のConverterは、基本的にこのクラスを継承して作成する。
*
* @version 1.5
* @author T.TSUCHIE
*
*/
public abstract class AbstractNumberCellConverter<T extends Number> extends AbstractCellConverter<T> {
@Override
public T toObject(final Cell cell, final FieldAdaptor adaptor, final XlsMapperConfig config) throws TypeBindException {
final XlsConverter converterAnno = adaptor.getLoadingAnnotation(XlsConverter.class);
final XlsNumberConverter anno = getLoadingAnnotation(adaptor);
T resultValue = null;
if(POIUtils.isEmptyCellContents(cell, config.getCellFormatter())) {
if(Utils.hasNotDefaultValue(converterAnno)) {
// デフォルト値を持たない場合
if(adaptor.getTargetClass().isPrimitive()) {
resultValue = getZeroValue();
}
} else {
String defaultValue = converterAnno.defaultValue();
try {
resultValue = parseNumber(defaultValue, createNumberFormat(anno), createMathContext(anno));
} catch(ParseException e) {
throw newTypeBindException(e, cell, adaptor, defaultValue)
.addAllMessageVars(createTypeErrorMessageVars(anno));
}
}
} else if(cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
// セルのタイプが数値型の場合はそのまま取得する。
try {
resultValue = convertNumber(cell.getNumericCellValue(), createMathContext(anno));
} catch(ArithmeticException e) {
throw newTypeBindException(e, cell, adaptor, cell)
.addAllMessageVars(createTypeErrorMessageVars(anno));
}
} else if(cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
// 式を評価して再帰的に処理する。
final Workbook workbook = cell.getSheet().getWorkbook();
final CreationHelper helper = workbook.getCreationHelper();
final FormulaEvaluator evaluator = helper.createFormulaEvaluator();
try {
// 再帰的に処理する
final Cell evalCell = evaluator.evaluateInCell(cell);
return toObject(evalCell, adaptor, config);
} catch(Exception e) {
throw newTypeBindException(e, cell, adaptor, cell)
.addAllMessageVars(createTypeErrorMessageVars(anno));
}
} else {
String cellValue = POIUtils.getCellContents(cell, config.getCellFormatter());
cellValue = Utils.trim(cellValue, converterAnno);
if(Utils.isNotEmpty(cellValue)) {
try {
resultValue = parseNumber(cellValue, createNumberFormat(anno), createMathContext(anno));
} catch(ParseException | ArithmeticException e) {
throw newTypeBindException(e, cell, adaptor, cellValue)
.addAllMessageVars(createTypeErrorMessageVars(anno));
}
}
}
if(resultValue != null) {
return resultValue;
} else if(adaptor.getTargetClass().isPrimitive()) {
return getZeroValue();
}
return null;
}
/**
* アノテーションから数値のフォーマッタを取得する。
* @param anno 引数がnull(アノテーションが設定されていない場合)は、nullを返す。
* @return アノテーションに書式が設定されていない場合はnullを返す。
*/
protected NumberFormat createNumberFormat(final XlsNumberConverter anno) {
final Locale locale;
if(anno.locale().isEmpty()) {
locale = Locale.getDefault();
} else {
locale = Utils.getLocale(anno.locale());
}
if(anno.javaPattern().isEmpty()) {
if(anno.currency().isEmpty()) {
return null;
} else {
// 通貨の場合
return NumberFormat.getCurrencyInstance(locale);
}
}
final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
final DecimalFormat format = new DecimalFormat(anno.javaPattern(), symbols);
format.setRoundingMode(RoundingMode.HALF_UP);
format.setParseBigDecimal(true);
if(Utils.isNotEmpty(anno.currency())) {
format.setCurrency(Currency.getInstance(anno.currency()));
}
return format;
}
/**
* 型変換エラー時のメッセージ変数の作成
*/
private Map<String, Object> createTypeErrorMessageVars(final XlsNumberConverter anno) {
final Map<String, Object> vars = new LinkedHashMap<>();
vars.put("javaPattern", anno.javaPattern());
vars.put("currency", anno.currency());
vars.put("locale", anno.locale());
vars.put("precision", anno.precision());
return vars;
}
/**
* アノテーションを元に、{@link MathContext}のインスタンスを取得する。
* <p>有効桁数、丸め方法を設定したものを返す。
* <p>有効桁数は、デフォルトではExcelに仕様に合わせて15桁。
* <p>丸め方法は、Excelに合わせて、{@link RoundingMode#HALF_UP}で固定。
* @param anno
* @return
*/
protected MathContext createMathContext(final XlsNumberConverter anno) {
if(anno.precision() > 0) {
return new MathContext(anno.precision(), RoundingMode.HALF_UP);
} else {
return new MathContext(15, RoundingMode.HALF_UP);
}
}
/**
* その型における数値型を返す。
* @param value
* @param context
* @return
*/
protected abstract T convertNumber(double value, MathContext context);
/**
* その型における数値型を返す。
* @param value
* @param context
* @return
*/
protected abstract T convertNumber(Number value, MathContext context);
/**
* その型における数値型を返す。
* @param value
* @return
*/
protected abstract T convertNumber(BigDecimal value);
/**
* 文字列をその型における数値型を返す。
* <p>アノテーション{@link XlsNumberConverter}でフォーマットが与えられている場合は、パースして返す。
* @param value
* @param format フォーマットが指定されていない場合はnullが渡される
* @param context 数値変換する際の設定
* @return
* @throws ParseException
*/
protected T parseNumber(final String value, final NumberFormat format, final MathContext context) throws ParseException {
if(format == null) {
final BigDecimal bg;
try {
// 文字列の時は、精度指定しない。
bg = new BigDecimal(value);
} catch(NumberFormatException e) {
throw new ParseException(String.format("Cannot parse '%s'", value), 0);
}
if(bg.doubleValue() < getMinValue()) {
throw new ParseException(String.format("'%s' cannot less than %f", value, getMinValue()), 0);
}
if(bg.doubleValue() > getMaxValue()) {
throw new ParseException(String.format("'%s' cannot greater than %f", value, getMaxValue()), 0);
}
return convertNumber(bg);
}
final ParsePosition position = new ParsePosition(0);
final Number result = (Number) format.parseObject(value, position);
if(position.getIndex() != value.length()) {
throw new ParseException(
String.format("Cannot parse '%s' using fromat %s", value, format.toString()), position.getIndex());
}
if(result.doubleValue() < getMinValue()) {
throw new ParseException(String.format("'%s' cannot less than %f", value, getMinValue()), 0);
}
if(result.doubleValue() > getMaxValue()) {
throw new ParseException(String.format("'%s' cannot greater than %f", value, getMaxValue()), 0);
}
if(result instanceof BigDecimal) {
// NumberFormatのインスタンスを作成する際に、DecimalFormat#setParseBigDecimal(true)としているため、戻り値がBigDecimalになる。
return convertNumber((BigDecimal) result);
} else {
return convertNumber(result, context);
}
}
/**
* その型におけるゼロ値を返す。
* @return
*/
protected abstract T getZeroValue();
/**
* その型における最大値を返す。
* @return
*/
protected abstract double getMaxValue();
/**
* その型における最小値を返す。
* @return
*/
protected abstract double getMinValue();
private XlsNumberConverter getDefaultNumberConverterAnnotation() {
return new XlsNumberConverter() {
@Override
public Class<? extends Annotation> annotationType() {
return XlsNumberConverter.class;
}
@Override
public String javaPattern() {
return "";
}
@Override
public String locale() {
return "";
}
@Override
public String currency() {
return "";
}
@Override
public int precision() {
return 15;
}
@Override
public String excelPattern() {
return "";
}
};
}
private XlsNumberConverter getLoadingAnnotation(final FieldAdaptor adaptor) {
XlsNumberConverter anno = adaptor.getLoadingAnnotation(XlsNumberConverter.class);
if(anno == null) {
anno = getDefaultNumberConverterAnnotation();
}
return anno;
}
private XlsNumberConverter getSavingAnnotation(final FieldAdaptor adaptor) {
XlsNumberConverter anno = adaptor.getSavingAnnotation(XlsNumberConverter.class);
if(anno == null) {
anno = getDefaultNumberConverterAnnotation();
}
return anno;
}
@Override
public Cell toCell(final FieldAdaptor adaptor, final Number targetValue, final Object targetBean,
final Sheet sheet, final int column, final int row,
final XlsMapperConfig config) throws XlsMapperException {
final XlsConverter converterAnno = adaptor.getSavingAnnotation(XlsConverter.class);
final XlsNumberConverter anno = getSavingAnnotation(adaptor);
final XlsFormula formulaAnno = adaptor.getSavingAnnotation(XlsFormula.class);
final boolean primaryFormula = formulaAnno == null ? false : formulaAnno.primary();
final Cell cell = POIUtils.getCell(sheet, column, row);
// セルの書式設定
if(converterAnno != null) {
POIUtils.wrapCellText(cell, converterAnno.wrapText());
POIUtils.shrinkToFit(cell, converterAnno.shrinkToFit());
}
Number value = targetValue;
// デフォルト値から値を設定する
if(value == null && Utils.hasDefaultValue(converterAnno)) {
final String defaultValue = converterAnno.defaultValue();
final NumberFormat formatter;
final MathContext context;
if(Utils.isNotEmpty(anno.javaPattern())) {
formatter = createNumberFormat(anno);
context = createMathContext(anno);
} else {
formatter = createNumberFormat(getDefaultNumberConverterAnnotation());
context = createMathContext(getDefaultNumberConverterAnnotation());
}
try {
value = parseNumber(defaultValue, formatter, context);
} catch (ParseException e) {
throw newTypeBindException(e, cell, adaptor, defaultValue)
.addAllMessageVars(createTypeErrorMessageVars(anno));
}
}
// セルの書式の設定
if(Utils.isNotEmpty(anno.excelPattern()) && !POIUtils.getCellFormatPattern(cell).equalsIgnoreCase(anno.excelPattern())) {
// 既にCell中に書式が設定され、それが異なる場合
CellStyle style = sheet.getWorkbook().createCellStyle();
style.cloneStyleFrom(cell.getCellStyle());
style.setDataFormat(POIUtils.getDataFormatIndex(sheet, anno.excelPattern()));
cell.setCellStyle(style);
}
if(value != null && !primaryFormula) {
cell.setCellValue(value.doubleValue());
} else if(formulaAnno != null) {
Utils.setupCellFormula(adaptor, formulaAnno, config, cell, targetBean);
} else {
cell.setCellType(Cell.CELL_TYPE_BLANK);
}
return cell;
}
}