AbstractNumberCellConverterFactory.java
package com.gh.mygreen.xlsmapper.cellconverter.impl;
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.ParsePosition;
import java.util.Currency;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import com.gh.mygreen.xlsmapper.Configuration;
import com.gh.mygreen.xlsmapper.annotation.XlsNumberConverter;
import com.gh.mygreen.xlsmapper.cellconverter.BaseCellConverter;
import com.gh.mygreen.xlsmapper.cellconverter.CellConverterFactory;
import com.gh.mygreen.xlsmapper.cellconverter.CellConverterFactorySupport;
import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessor;
import com.gh.mygreen.xlsmapper.textformatter.TextFormatter;
import com.gh.mygreen.xlsmapper.textformatter.TextParseException;
import com.gh.mygreen.xlsmapper.util.ArgUtils;
import com.gh.mygreen.xlsmapper.util.Utils;
/**
* 数値型のCellConverterを作成するためのベースクラス。
*
* @since 2.0
* @author T.TSUCHIE
*
*/
public abstract class AbstractNumberCellConverterFactory<T extends Number> extends CellConverterFactorySupport<T>
implements CellConverterFactory<T> {
@Override
protected void setupCustom(final BaseCellConverter<T> cellConverter, final FieldAccessor field, final Configuration config) {
ArgUtils.instanceOf(cellConverter, AbstractNumberCellConverter.class, "cellConverter");
if(cellConverter instanceof AbstractNumberCellConverter) {
final AbstractNumberCellConverter<T> numberCellConverter = (AbstractNumberCellConverter<T>)cellConverter;
// 書き込み時のセルの書式を設定する
Optional<XlsNumberConverter> convertAnno = field.getAnnotation(XlsNumberConverter.class);
Optional<String> excelPattern = getExcelPattern(convertAnno);
excelPattern.ifPresent(pattern -> numberCellConverter.setExcelPattern(pattern));
numberCellConverter.setMathContext(createMathContext(convertAnno));
}
}
@Override
protected TextFormatter<T> createTextFormatter(final FieldAccessor field, final Configuration config) {
final Optional<XlsNumberConverter> convertAnno = field.getAnnotation(XlsNumberConverter.class);
final Optional<NumberFormat> numberFormat = createFormatter(convertAnno);
final MathContext mathContext = createMathContext(convertAnno);
if(numberFormat.isPresent()) {
final NumberFormat fromatter = numberFormat.get();
// 書式が指定されている場合
return new TextFormatter<T>() {
@Override
public T parse(final String text) {
ParsePosition position = new ParsePosition(0);
BigDecimal number = (BigDecimal) fromatter.parse(text, position);
if(position.getIndex() != text.length()) {
throw new TextParseException(text, field.getType());
}
try {
number = number.setScale(mathContext.getPrecision(), mathContext.getRoundingMode());
return convertTypeValue(number);
} catch(NumberFormatException | ArithmeticException e) {
final Map<String, Object> vars = new HashMap<>();
vars.put("javaPattern", getJavaPattern(convertAnno).orElse(null));
vars.put("excelPattern", getExcelPattern(convertAnno).orElse(null));
throw new TextParseException(text, field.getType(), e, vars);
}
}
@Override
public String format(final T value) {
return fromatter.format(value);
}
};
} else {
return new TextFormatter<T>() {
@Override
public T parse(final String text) {
/*
* 有効桁数15桁を超えている場合、Excelの場合は、切り捨てによる丸めが入るが、
* Javaではそのまま、HALF_UPとなり、オーバーフローが起こる場合がある。
*/
try {
BigDecimal value = new BigDecimal(text, mathContext);
return convertTypeValue(value);
} catch(NumberFormatException | ArithmeticException e) {
throw new TextParseException(text, field.getType(), e);
}
}
@Override
public String format(final T value) {
return value.toString();
}
};
}
}
/**
* その型における型に変換する
* @param value 変換対象の値
* @return 変換後の値
* @throws NumberFormatException
* @throws ArithmeticException
*/
protected abstract T convertTypeValue(BigDecimal value) throws NumberFormatException, ArithmeticException;
/**
* アノテーションから数値のフォーマッタを取得する。
* @param convertAnno 引数がnull(アノテーションが設定されていない場合)は、nullを返す。
* @return アノテーションに書式が設定されていない場合は空を返す。
*/
private Optional<NumberFormat> createFormatter(final Optional<XlsNumberConverter> convertAnno) {
if(!convertAnno.isPresent()) {
return Optional.empty();
}
final Optional<String> javaPattern = getJavaPattern(convertAnno);
final Locale locale = Utils.getLocale(convertAnno.get().locale());
final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
final Optional<Currency> currency = convertAnno.get().currency().isEmpty() ? Optional.empty()
: Optional.of(Currency.getInstance(convertAnno.get().currency()));
if(!javaPattern.isPresent()) {
if(!convertAnno.get().currency().isEmpty()) {
// 通貨の場合
DecimalFormat formatter = (DecimalFormat)NumberFormat.getCurrencyInstance(locale);
formatter.setParseBigDecimal(true);
formatter.setDecimalFormatSymbols(symbols);
currency.ifPresent(c -> formatter.setCurrency(c));
return Optional.of(formatter);
} else {
return Optional.empty();
}
}
final DecimalFormat formatter = new DecimalFormat(javaPattern.get(), symbols);
formatter.setRoundingMode(RoundingMode.HALF_UP);
formatter.setParseBigDecimal(true);
currency.ifPresent(c -> formatter.setCurrency(c));
return Optional.of(formatter);
}
private Optional<String> getJavaPattern(final Optional<XlsNumberConverter> converterAnno) {
if(!converterAnno.isPresent()) {
return Optional.empty();
}
String pattern = converterAnno.get().javaPattern();
if(pattern.isEmpty()) {
return Optional.empty();
}
return Optional.of(pattern);
}
private Optional<String> getExcelPattern(final Optional<XlsNumberConverter> converterAnno) {
if(!converterAnno.isPresent()) {
return Optional.empty();
}
String pattern = converterAnno.get().excelPattern();
if(pattern.isEmpty()) {
return Optional.empty();
}
return Optional.of(pattern);
}
/**
* アノテーションを元に、{@link MathContext}のインスタンスを取得する。
* <p>有効桁数、丸め方法を設定したものを返す。
* <p>有効桁数は、デフォルトでは無期限にする。
* <p>丸め方法は、Excelに合わせて、{@link RoundingMode#HALF_UP}で固定。
* @param convertAnno
* @return
*/
private MathContext createMathContext(final Optional<XlsNumberConverter> convertAnno) {
if(convertAnno.isPresent() && convertAnno.get().precision() > 0) {
return new MathContext(convertAnno.get().precision(), RoundingMode.HALF_UP);
} else {
//アノテーションがない場合は、制限なし。
return MathContext.UNLIMITED;
}
}
}