CellField.java
package com.gh.mygreen.xlsmapper.validation.fieldvalidation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.gh.mygreen.xlsmapper.fieldaccessor.LabelGetterFactory;
import com.gh.mygreen.xlsmapper.fieldaccessor.PositionGetterFactory;
import com.gh.mygreen.xlsmapper.util.ArgUtils;
import com.gh.mygreen.xlsmapper.util.CellPosition;
import com.gh.mygreen.xlsmapper.validation.FieldError;
import com.gh.mygreen.xlsmapper.validation.FieldErrorBuilder;
import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
/**
* 1つの項目(フィールド)に対する入力値チェックをするためのクラス。
* <p>型変換などにおけるバインドエラーなどはシートの読み込み時に行われます。
* <p>必須エラーのメッセージキーは、「fieldError.required」。
*
* @version 0.5
* @author T.TSUCHIE
* @param <T> チェック対象の値のタイプ
*
*/
public class CellField<T> {
private static final PositionGetterFactory positionGetterFactory = new PositionGetterFactory();
private static final LabelGetterFactory labelGetterFactory = new LabelGetterFactory();
private final SheetBindingErrors<?> errors;
/**
* フィールドの名称
*/
private final String fieldName;
/**
* フィールドが定義されているBeanクラスのインスタンス
*/
private Object beanObj;
/**
* フィールドの値
*/
private T fieldValue;
/**
* フィールドのJavaBean上のパス
*/
private String fieldPath;
/**
* セルの位置情報。
* Beanに定義されたプロパティ情報から取得する。
* 定義されていない場合は、nullが設定される。
*/
private CellPosition position;
/**
* セルのラベル情報
* Beanに定義されたプロパティ情報から取得する。
* 定義されていない場合は、nullが設定される。
*/
private String label;
/**
* 必須かどうか
*/
private boolean required;
private List<FieldValidator<T>> validators;
private FieldFormatter<T> formatter;
private Class<T> fieldType;
/**
* 指定されたフィールドの名称に対応するオブジェクトを構築します。
* @param fieldName フィールドの名称。現在のBeanに対するフィールドの相対パスを指定します。
* @param errors エラー情報
*/
public CellField(final String fieldName, final SheetBindingErrors<?> errors) {
ArgUtils.notEmpty(fieldName, "fieldName");
ArgUtils.notNull(errors, "errors");
this.fieldName = fieldName;
this.errors = errors;
init();
}
@SuppressWarnings("unchecked")
private void init() {
this.beanObj = errors.getValue();
this.fieldValue = (T)errors.getFieldValue(fieldName);
this.fieldType = (Class<T>)errors.getFieldType(fieldName);
this.fieldPath = errors.buildFieldPath(fieldName);
Optional<CellPosition> position = positionGetterFactory.create(beanObj.getClass(), fieldName)
.map(getter -> getter.get(beanObj)).orElse(Optional.empty());
position.ifPresent(p -> setPosition(p));
Optional<String> label = labelGetterFactory.create(beanObj.getClass(), fieldName)
.map(getter -> getter.get(beanObj)).orElse(Optional.empty());
label.ifPresent(l -> setLabel(l));
this.required = false;
this.validators = new ArrayList<>();
this.formatter = errors.findFieldFormatter(fieldName, fieldType);
}
/**
* 値が必須かの設定を行う。
* @param required 必須チェックを行いたい場合、「true」を設定する。
* @return 自身のインスタンス。メソッドチェーンで記述する。
*/
public CellField<T> setRequired(final boolean required) {
this.required = required;
return this;
}
/**
* 値が必須かチェックを行うかどうか。
* @return true: 必須入力チェックを行う。
* 初期値は、非必須(オプション)です。
*/
public boolean isRequired() {
return required;
}
/**
* {@link FieldValidator} を追加する。
* @param validator validatorのインスタンス。
* @return 自身のインスタンス。
* @throws NullPointerException validator is null.
*/
public CellField<T> add(final FieldValidator<T> validator) {
ArgUtils.notNull(validator, "validator");
this.validators.add(validator);
return this;
}
/**
* 複数の{@link FieldValidator} を追加する。
* @param validators 複数のvalidatorのインスタンス。
* @return 自身のインスタンス。
* @throws NullPointerException validator is null.
*/
public CellField<T> add(final List<FieldValidator<T>> validators) {
if(validators.isEmpty()) {
return this;
}
for(FieldValidator<T> validator : validators) {
add(validator);
}
return this;
}
/**
* 現在の{@link FieldValidator}を取得する。
* @return 現在設定されている{@link FieldValidator}。
*/
public List<FieldValidator<T>> getValidators() {
return validators;
}
/**
* グループなどのヒントを指定して、入力値の検証を行う。
* <p>判定結果は、{@link #hasErrors()}で確認します。</p>
* <p>型変換エラーなどが既に存在するときには、処理は終了します。</p>
*
* @param groups 検証するときのヒントとなるグループ。
* @return 自身のインスタンス。
*/
public CellField<T> validate(final Class<?>... groups) {
// 既に型変換エラーなどがある場合、値が設定されていないため処理を終了します。
if(hasErrors()) {
return this;
}
// 必須チェック
if(!validateForRequired()) {
return this;
}
final List<Class<?>> hints = Arrays.asList(groups);
if(getValidators() != null && !getValidators().isEmpty()) {
for(FieldValidator<T> validator : getValidators()) {
if(!validator.validate(this, hints)) {
return this;
}
}
}
return this;
}
/**
* 必須エラーのメッセージキーを取得する。
* <p>キー名は、「fieldError.required」。
* @return
*/
protected String getMessageKeyRequired() {
return "cellFieldError.required";
}
/**
* 必須チェックを行う。
* @return trueの場合、必須エラーでない。
*/
protected boolean validateForRequired() {
if(isRequired() && isInputEmpty()) {
errors.createFieldError(fieldName, getMessageKeyRequired())
.address(getPosition())
.label(getLabel())
.variables("validatedValue", getValue())
.buildAndAddError();
return false;
}
return true;
}
/**
* エラーを追加する。
* @param errorCode エラーコード
*/
public void rejectValue(final String errorCode) {
rejectValue(errorCode, Collections.emptyMap());
}
/**
* エラーを追加する
* @param errorCode エラコード
* @param variables エラーメッセージ中の変数
*/
public void rejectValue(final String errorCode, final Map<String, Object> variables) {
final String codes[] = errors.generateMessageCodes(errorCode, fieldPath, fieldType);
final FieldError error = new FieldErrorBuilder(errors.getObjectName(), fieldPath, codes)
.sheetName(errors.getSheetName())
.rejectedValue(fieldValue)
.variables(variables)
.address(position)
.label(label)
.build();
errors.addError(error);
}
/**
* フィールドの値が空かどうか。
* <p>値がnullまたは、文字列の場合空文字のとき、空と判定する。
* @return trueの場合、値は空。
*/
public boolean isInputEmpty() {
if(fieldValue == null || fieldValue.toString().isEmpty()) {
return true;
}
return false;
}
/**
* フィールドに対してエラーが存在するかどうか。
* @return trueの場合、エラーが存在する。
*/
public boolean hasErrors() {
return errors.hasFieldErrors(fieldName);
}
/**
* フィールドに対してエラーが存在しなかどうか。
* @return trueの場合、エラーが存在しない。
*/
public boolean hasNotErrors() {
return !hasErrors();
}
/**
* エラー情報を取得する。
* @return エラー情報
*/
public SheetBindingErrors<?> getBindingErrors() {
return errors;
}
/**
* フィールドの名称を取得する
* @return フィールドの名称
*/
public String getFieldName() {
return fieldName;
}
/**
* フィールドの値を取得する。
* @return フィールドの値。
*/
public T getValue() {
return fieldValue;
}
/**
* フィールドのクラスタイプを取得する。
* @return クラスタイプ
*/
public Class<T> getType() {
return fieldType;
}
/**
* フィールドのJavaBean上のパスを取得する。
* @return フィールドのJavaBean上のパス
*/
public String getFieldPath() {
return fieldPath;
}
/**
* セルの位置情報を取得します。
* <p>位置情報の取得用のフィールドやメソッドがbeanに定義されている場合は、コンストラクタの呼び出し時に設定されています。</p>
* @return 自身のインスタンス。
*/
public CellPosition getPosition() {
return position;
}
/**
* セルの位置情報を設定します。
* @param position セルの位置情報
*/
public void setPosition(CellPosition position) {
this.position = position;
}
/**
* セルのラベル情報を取得します。
* <p>ラベル情報の取得用のフィールドやメソッドがbeanに定義されている場合は、コンストラクタの呼び出し時に設定されています。</p>
* @return 自身のインスタンス。
*/
public String getLabel() {
return label;
}
/**
* セルのラベル情報を設定します。
* @param label セルのラベル情報
*/
public void setLabel(String label) {
this.label = label;
}
/**
* フォーマッタを取得する。
* @return フォーマッタ。
* デフォルトでは、フィールドのクラスタイプ、付与されたアノテーションを元にしたもの。
*
*/
public FieldFormatter<T> getFormatter() {
return formatter;
}
/**
* フォーマッタを設定する。
* @param formatter
*/
public void setFormatter(FieldFormatter<T> formatter) {
this.formatter = formatter;
}
}