package com.gh.mygreen.xlsmapper.validation.fieldvalidation;
import java.awt.Point;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.validation.Errors;
import com.gh.mygreen.xlsmapper.ArgUtils;
import com.gh.mygreen.xlsmapper.PropertyNavigator;
import com.gh.mygreen.xlsmapper.Utils;
import com.gh.mygreen.xlsmapper.validation.FieldError;
import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
/**
* 1つの項目(フィールド)に対する入力値チェックをするためのクラス。
* <p>型変換などにおけるバインドエラーなどはシートの読み込み時に行われます。
* <p>必須エラーのメッセージキーは、「fieldError.required」。
*
* @version 0.5
* @author T.TSUCHIE
* @param <T> チェック対象の値のタイプ
*
*/
public class CellField<T> {
/**
* プロパティにアクセスするための式言語。
* ・OGNLを利用し、private/protectedなどのフィールドにもアクセス可能にする。
*/
private static final PropertyNavigator propertyNavigator = new PropertyNavigator();
static {
propertyNavigator.setAllowPrivate(true);
propertyNavigator.setIgnoreNull(true);
propertyNavigator.setIgnoreNotFoundKey(true);
propertyNavigator.setCacheWithPath(true);
}
/** フィールド名(チェック対象のプロパティ名) */
final private String name;
/** チェック対象の値 */
private T value;
/** セルのアドレス */
private Point cellAddress;
/** 必須かどうか */
private boolean required;
/** ラベル */
private String label;
/** フィールドValidator */
private List<FieldValidator<T>> validators;
public CellField(final String name) {
this(name, (T)null);
}
/**
* フィールド名とその値を指定するコンストラクタ
* @param fieldName フィールドの名称
* @param fieldValue フィールドの値
* @throws IllegalArgumentException fieledName is empty.
*/
public CellField(final String fieldName, final T fieldValue) {
ArgUtils.notEmpty(fieldName, "fieldName");
this.name = fieldName;
setValue(fieldValue);
this.validators = new ArrayList<FieldValidator<T>>();
}
/**
* Commandから指定したフィールド名(プロパティ名)の値をBeanWrapperにて自動的に取得するコンストラクタ。
* @param targetObj Commandのインスタンス。
* @param fieldName Commandにおけるプロパティ名。
* @throws IllegalArgumentException commandObj is null.
* @throws IllegalArgumentException fieldName is empty.
*/
public CellField(final Object targetObj, final String fieldName) {
ArgUtils.notNull(targetObj, "commandObj");
ArgUtils.notEmpty(fieldName, "fieldName");
this.name = fieldName;
@SuppressWarnings("unchecked")
final T fieldValue = (T) propertyNavigator.getProperty(targetObj, fieldName);
setValue(fieldValue);
setCellAddress(Utils.getPosition(targetObj, fieldName));
setLabel(Utils.getLabel(targetObj, fieldName));
this.validators = new ArrayList<FieldValidator<T>>();
}
/**
* {@link FieldValidator} を追加する。
* @param validator validatorのインスタンス。
* @return 自身のインスタンス。
* @throws IllegalArgumentException validator is null.
*/
public CellField<T> add(final FieldValidator<T> validator) {
ArgUtils.notNull(validator, "validator");
validators.add(validator);
return this;
}
/**
* 現在のValidatorを取得する。
* @return
*/
public List<FieldValidator<T>> getValidators() {
return validators;
}
/**
* 入力値チェックを行う。
* <p>既に、引数のerrorsの中に自身に関するエラーがある場合は無視する。
* <p>チェック順は、(1)必須チェック、(2)追加したFieldValidatorの順。
*
* @param errors エラーオブジェクト。
* @throws IllegalArgumentException errors is null.
*/
public CellField<T> validate(final SheetBindingErrors errors) {
ArgUtils.notNull(errors, "errors");
if(hasErrors(errors)) {
// 既にフィールドに対するエラーがある場合
return this;
}
if(!validateAsRequired(errors)) {
// 必須エラーの場合
appendSheetInfo(errors);
return this;
}
if(getValidators() != null && !getValidators().isEmpty()) {
// 各種入力値チェックを行う。
for(FieldValidator<T> validator : getValidators()) {
if(!invokeValidate(validator, errors)) {
// エラーがある場合
break;
}
}
}
appendSheetInfo(errors);
return this;
}
/**
* 現在のエラーにシート情報を補完する
* @param errors
*/
protected void appendSheetInfo(final SheetBindingErrors errors) {
for(FieldError error : errors.getFieldErrors(getName())) {
error.setLabel(getLabel());
}
}
/**
* 指定したFieldValidatorを実行し、値を検証する。
* @param validator
* @param errors
* @return
*/
protected boolean invokeValidate(final FieldValidator<T> validator, final SheetBindingErrors errors) {
if(validator instanceof CellFieldValidator && getCellAddress() != null) {
CellFieldValidator<T> sheetValidator = (CellFieldValidator<T>) validator;
return sheetValidator.validate(getName(), getValue(), getCellAddress(), errors);
} else {
return validator.validate(getName(), getValue(), errors);
}
}
/**
* 必須チェックを行う。
*
* @param errors
* @return true:エラーがない場合。既にエラーがある場合。
*/
protected boolean validateAsRequired(final SheetBindingErrors errors) {
if(isInputEmpty()) {
// 必須エラーチェックを行う場合
if(isRequired()) {
Map<String, Object> vars = new LinkedHashMap<>();
vars.put("validatedValue", getValue());
errors.rejectSheetValue(getName(), getCellAddress(), getMessageKeyRequired(), vars);
return false;
}
return true;
}
// エラーがない場合
return true;
}
/**
* 必須エラーのメッセージキーを取得する。
* <p>キー名は、「fieldError.required」。
* @return
*/
protected String getMessageKeyRequired() {
return "cellFieldError.required";
}
/**
* フィールドの値が空 or nullかどうか。
* @return
*/
public boolean isInputEmpty() {
return (getValue() == null || getValue().toString().isEmpty());
}
/**
* フィールドの値が空 or null出ない場合。
* @return
*/
public boolean isNotInputEmpty() {
return !isInputEmpty();
}
/**
* 検証対象の値を設定する。
* @param value 検証対象の値。
* @return
*/
protected CellField<T> setValue(final T value) {
this.value = value;
return this;
}
/**
* 検証対象の値を取得する。
* @return
*/
public T getValue() {
return value;
}
/**
* フィールドの名前を取得する。
* @return
*/
public String getName() {
return name;
}
/**
* 値が必須かの設定を行う。
* @param required 必須チェックを行いたい場合、「true」を設定する。
* @return
*/
public CellField<T> setRequired(final boolean required) {
this.required = required;
return this;
}
/**
* 値が必須かチェックを行うかどうか。
* @return true: 必須入力チェックを行う。
*/
public boolean isRequired() {
return required;
}
/**
* 自身のフィールドに対してフィールドエラーを持つかどうか。
* <p>{@link Errors#hasFieldErrors(String)}を呼び出す。
* @param errors Springのエラーオブジェクト。
* @return true:エラーを持つ場合。
* @throws IllegalArgumentException errors == null.
*/
public boolean hasErrors(final SheetBindingErrors errors) {
ArgUtils.notNull(errors, "errors");
return errors.hasFieldErrors(getName());
}
/**
* 自身のフィールドに対してエラーを持たないかどうか
* @param errors
* @return
*/
public boolean hasNotErrors(SheetBindingErrors errors) {
return !hasErrors(errors);
}
public Point getCellAddress() {
return cellAddress;
}
public CellField<T> setCellAddress(Point cellAddress) {
this.cellAddress = cellAddress;
return this;
}
public String getLabel() {
return label;
}
public CellField<T> setLabel(String label) {
this.label = label;
return this;
}
}