package com.gh.mygreen.xlsmapper.cellconvert.converter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
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.XlsEnumConverter;
import com.gh.mygreen.xlsmapper.annotation.XlsFormula;
import com.gh.mygreen.xlsmapper.cellconvert.AbstractCellConverter;
import com.gh.mygreen.xlsmapper.cellconvert.ConversionException;
import com.gh.mygreen.xlsmapper.fieldprocessor.FieldAdaptor;
/**
* 列挙型のConverter。
* <p>一度読み込んだ列挙型の情報はキャッシュする。列挙型はstaticであるため、動的に変更できないため。
*
* @version 1.5
* @author T.TSUCHIE
*
*/
@SuppressWarnings("rawtypes")
public class EnumCellConverter extends AbstractCellConverter<Enum> {
/**
* 列挙科型のクラスとその値とのマップ。(キャッシュデータ)
* <p>key=列挙型のクラスタイプ、value=列挙型の各項目の文字列形式とオブジェクト形式の値のマップ。
*/
private Map<Class<?>, Map<String, Enum>> cacheData;
public EnumCellConverter() {
this.cacheData = new ConcurrentHashMap<Class<?>, Map<String,Enum>>();
}
@SuppressWarnings({"unchecked"})
@Override
public Enum<?> toObject(final Cell cell, final FieldAdaptor adaptor, final XlsMapperConfig config) throws XlsMapperException {
final XlsConverter converterAnno = adaptor.getLoadingAnnotation(XlsConverter.class);
final XlsEnumConverter anno = getLoadingAnnotation(adaptor);
String cellValue = POIUtils.getCellContents(cell, config.getCellFormatter());
cellValue = Utils.trim(cellValue, converterAnno);
if(Utils.isEmpty(cellValue) && Utils.hasNotDefaultValue(converterAnno)) {
return null;
}
cellValue = Utils.getDefaultValueIfEmpty(cellValue, converterAnno);
final Class<Enum> taretClass = (Class<Enum>) adaptor.getTargetClass();
Enum<?> resultValue = convertToObject(cellValue, taretClass, anno);
if(resultValue == null && Utils.isNotEmpty(cellValue)) {
// 値があり変換できない場合
throw newTypeBindException(cell, adaptor, cellValue)
.addAllMessageVars(createTypeErrorMessageVars(taretClass, anno));
}
return resultValue;
}
/**
* 型変換エラー時のメッセージ変数の作成
* @throws ConversionException
*/
private Map<String, Object> createTypeErrorMessageVars(final Class<Enum> taretClass, final XlsEnumConverter anno) throws ConversionException {
final Map<String, Object> vars = new LinkedHashMap<>();
vars.put("candidateValues", Utils.join(getLoadingAvailableValue(taretClass, anno), ", "));
vars.put("ignoreCase", anno.ignoreCase());
return vars;
}
private XlsEnumConverter getDefaultEnumConverterAnnotation() {
return new XlsEnumConverter() {
@Override
public Class<? extends Annotation> annotationType() {
return XlsEnumConverter.class;
}
@Override
public boolean ignoreCase() {
return false;
}
@Override
public String valueMethodName() {
return "";
}
};
}
private XlsEnumConverter getLoadingAnnotation(final FieldAdaptor adaptor) {
XlsEnumConverter anno = adaptor.getLoadingAnnotation(XlsEnumConverter.class);
if(anno == null) {
anno = getDefaultEnumConverterAnnotation();
}
return anno;
}
private XlsEnumConverter getSavingAnnotation(final FieldAdaptor adaptor) {
XlsEnumConverter anno = adaptor.getSavingAnnotation(XlsEnumConverter.class);
if(anno == null) {
anno = getDefaultEnumConverterAnnotation();
}
return anno;
}
/**
* 読み込み時の入力の候補となる値を取得する
* @param clazz 列挙型の値
* @param anno
* @return
* @throws ConversionException
*/
private Collection<String> getLoadingAvailableValue(final Class<Enum> clazz, final XlsEnumConverter anno) throws ConversionException {
final Set<String> values = new LinkedHashSet<String>();
final Map<String, Enum> map = getEnumValueMapFromCache(clazz);
for(Map.Entry<String, Enum> entry : map.entrySet()) {
if(anno.valueMethodName().isEmpty()) {
values.add(entry.getKey());
} else {
try {
final Method method = clazz.getMethod(anno.valueMethodName(), new Class[]{});
method.setAccessible(true);
final String value = method.invoke(entry.getValue(), new Object[]{}).toString();
values.add(value);
} catch(Exception e) {
throw new ConversionException(
String.format("Not found Enum method '%s#%s()'.", clazz.getName(), anno.valueMethodName()),
e, clazz);
}
}
}
return values;
}
/**
* 列挙型のキーと値のマップをキャッシュから取得する。
* <p>key=列挙型のname()の値。value=列挙型の各項目のオブジェクト。
* <p>キャッシュ上に存在しなければ、新しく情報を作成しキャッシュに追加する。
* @param clazz
* @return
*/
private synchronized Map<String, Enum> getEnumValueMapFromCache(final Class<Enum> clazz) {
if(cacheData.containsKey(clazz)) {
return cacheData.get(clazz);
}
final Map<String, Enum> map = createEnumValueMap(clazz);
cacheData.put(clazz, map);
return map;
}
/**
* 列挙型のマップを作成する。
*
* @param clazz 列挙型のクラス。
* @return key=項目名({@link Enum#name()}メソッドの値)、value=列挙型の項目オブジェクト。
*/
@SuppressWarnings({"unchecked"})
private Map<String, Enum> createEnumValueMap(final Class<Enum> clazz) {
final Map<String, Enum> map = new LinkedHashMap<>();
final EnumSet set = EnumSet.allOf(clazz);
final Iterator<Enum> it = set.iterator();
while(it.hasNext()) {
final Enum e = it.next();
map.put(e.name(), e);
}
return Collections.unmodifiableMap(map);
}
private Enum<?> convertToObject(final String value, final Class<Enum> clazz, final XlsEnumConverter anno) throws ConversionException {
final Map<String, Enum> map = getEnumValueMapFromCache(clazz);
for(Map.Entry<String, Enum> entry : map.entrySet()) {
final String key;
if(anno.valueMethodName().isEmpty()) {
key = entry.getKey();
} else {
try {
final Method method = clazz.getMethod(anno.valueMethodName(), new Class[]{});
method.setAccessible(true);
key = method.invoke(entry.getValue(), new Object[]{}).toString();
} catch(Exception e) {
throw new ConversionException(
String.format("Not found Enum method '%s#%s()'.", clazz.getName(), anno.valueMethodName()),
e, clazz);
}
}
if(anno.ignoreCase() && value.equalsIgnoreCase(key)) {
return entry.getValue();
} else if(value.equals(key)) {
return entry.getValue();
}
}
return null;
}
private String convertToString(final Enum<?> value, final Class<Enum> clazz, final XlsEnumConverter anno) throws ConversionException {
final Map<String, Enum> map = getEnumValueMapFromCache(clazz);
for(Map.Entry<String, Enum> entry : map.entrySet()) {
if(entry.getValue() != value) {
continue;
}
if(anno.valueMethodName().isEmpty()) {
return value.name();
} else {
try {
final Method method = clazz.getMethod(anno.valueMethodName(), new Class[]{});
method.setAccessible(true);
return method.invoke(entry.getValue(), new Object[]{}).toString();
} catch(Exception e) {
throw new ConversionException(
String.format("Not found Enum method '%s#%s()'.", clazz.getName(), anno.valueMethodName()),
e, clazz);
}
}
}
return null;
}
@Override
public Cell toCell(final FieldAdaptor adaptor, final Enum targetValue, final Object targetBean,
final Sheet sheet, final int column, final int row, final XlsMapperConfig config) throws XlsMapperException {
final XlsConverter converterAnno = adaptor.getLoadingAnnotation(XlsConverter.class);
final XlsEnumConverter anno = getSavingAnnotation(adaptor);
final XlsFormula formulaAnno = adaptor.getSavingAnnotation(XlsFormula.class);
final boolean primaryFormula = formulaAnno == null ? false : formulaAnno.primary();
final Class<Enum> taretClass = (Class<Enum>) adaptor.getTargetClass();
final Cell cell = POIUtils.getCell(sheet, column, row);
// セルの書式設定
if(converterAnno != null) {
POIUtils.wrapCellText(cell, converterAnno.wrapText());
POIUtils.shrinkToFit(cell, converterAnno.shrinkToFit());
}
Enum value = targetValue;
// デフォルト値から値を設定する
if(value == null && Utils.hasDefaultValue(converterAnno)) {
value = convertToObject(Utils.getDefaultValue(converterAnno), (Class<Enum>) adaptor.getTargetClass(), anno);
// 初期値が設定されているが、変換できないような時はエラーとする
if(value == null) {
throw newTypeBindException(cell, adaptor, Utils.getDefaultValue(converterAnno))
.addAllMessageVars(createTypeErrorMessageVars(taretClass, anno));
}
}
if(value != null && !primaryFormula) {
final String cellValue = convertToString(value, (Class<Enum>) adaptor.getTargetClass(), anno);
cell.setCellValue(cellValue);
} else if(formulaAnno != null) {
Utils.setupCellFormula(adaptor, formulaAnno, config, cell, targetBean);
} else {
cell.setCellType(Cell.CELL_TYPE_BLANK);
}
return cell;
}
}