FieldFormatterRegistry.java
package com.gh.mygreen.xlsmapper.validation.fieldvalidation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.gh.mygreen.xlsmapper.util.ArgUtils;
/**
* {@link FieldFormatter}を管理するためのクラス。
* <p>フィールドの値を検証する際に、フィールドの値をフォーマットしてエラーメッセージに埋め込むために使用します。</p>
*
* @since 2.0
* @author T.TSUCHIE
*
*/
public class FieldFormatterRegistry {
/**
* クラスタイプで関連づけられたフォーマッタ
*/
private Map<Class<?>, FieldFormatter<?>> typeMap = new HashMap<>();
/**
* フィールドパスで関連づけられたフォーマッタ
*/
private Map<String, FormatterHolder> pathMap = new HashMap<>();
/**
* 登録されているフォーマッタを初期化する。
*/
public void init() {
this.typeMap.clear();
this.pathMap.clear();
}
/**
* クラスタイプを指定してフォーマッタを登録する。
* @param <T> 処理対象のタイプ
* @param requiredType クラスタイプ
* @param formatter 登録対象のフォーマッタ
* @throws IllegalArgumentException {@literal requiredType == null or formatter == null}
*/
public <T> void registerFormatter(final Class<T> requiredType, FieldFormatter<T> formatter) {
ArgUtils.notNull(requiredType, "requiredType");
ArgUtils.notNull(formatter, "formatter");
this.typeMap.put(requiredType, formatter);
}
/**
* フィールドパスとクラスタイプを指定してフォーマッタを登録する。
* @param <T> 処理対象のタイプ
* @param fieldPath
* @param requiredType クラスタイプ
* @param formatter 登録対象のフォーマッタ
*/
public <T> void registerFormatter(final String fieldPath, final Class<T> requiredType, FieldFormatter<T> formatter) {
ArgUtils.notEmpty(fieldPath, "fieldPath");
ArgUtils.notNull(requiredType, "requiredType");
ArgUtils.notNull(formatter, "formatter");
this.pathMap.put(fieldPath, new FormatterHolder(requiredType, formatter));
}
/**
* 指定したクラスタイプに対する{@link FieldFormatter}を取得する。
* @param <T> クラスタイプ
* @param requiredType 取得したいクラスタイプ
* @return 見つからない場合は、nullを返す。
*/
@SuppressWarnings("unchecked")
public <T> FieldFormatter<T> getFormatter(final Class<T> requiredType) {
return (FieldFormatter<T>)typeMap.get(requiredType);
}
/**
* 指定したパスとクラスタイプに対する{@link FieldFormatter}を取得する。
* <p>引数「fieldPath」に完全に一致するフォーマッタを取得します。</p>
* @param fieldPath フィールドパス。
* @param requiredType 取得したいクラスタイプ
* @return 見つからない場合は、nullを返す。
*/
public <T> FieldFormatter<T> getFormatter(final String fieldPath, final Class<T> requiredType) {
FormatterHolder holder = pathMap.get(fieldPath);
return holder != null ? holder.getFormatter(requiredType) : null;
}
/**
* 指定したパスとクラスタイプに対する{@link FieldFormatter}を取得する。
* <p>ただし、リストのインデックスやキーが含まれている場合、引数「fieldPath」からそれらを取り除いた値で比較する。
* <br>一致するフィールドに対するフォーマッタが見つからない場合は、タイプのみで比較する。
* </p>
* @param fieldPath フィールドパス。
* @param requiredType 取得したいクラスタイプ
* @return 見つからない場合は、nullを返す。
*/
public <T> FieldFormatter<T> findFormatter(final String fieldPath, final Class<T> requiredType) {
// 完全なパスで比較
FieldFormatter<T> formatter = getFormatter(fieldPath, requiredType);
if(formatter != null) {
return formatter;
}
// インデックスを除去した形式で比較
final List<String> strippedPaths = new ArrayList<>();
addStrippedPropertyPaths(strippedPaths, "", fieldPath);
for(String strippedPath : strippedPaths) {
formatter = getFormatter(strippedPath, requiredType);
if(formatter != null) {
return formatter;
}
}
// 見つからない場合は、タイプのみで比較した物を取得する
return getFormatter(requiredType);
}
/**
* パスからリストのインデックス([1])やマップのキー([key])を除去したものを構成する。
* <p>SpringFrameworkの「PropertyEditorRegistrySupport#addStrippedPropertyPaths(...)」の処理</p>
* @param strippedPaths 除去したパス
* @param nestedPath 現在のネストしたパス
* @param propertyPath 処理対象のパス
*/
public void addStrippedPropertyPaths(List<String> strippedPaths, String nestedPath, String propertyPath) {
final int startIndex = propertyPath.indexOf('[');
if (startIndex != -1) {
final int endIndex = propertyPath.indexOf(']');
if (endIndex != -1) {
final String prefix = propertyPath.substring(0, startIndex);
final String key = propertyPath.substring(startIndex, endIndex + 1);
final String suffix = propertyPath.substring(endIndex + 1, propertyPath.length());
// Strip the first key.
strippedPaths.add(nestedPath + prefix + suffix);
// Search for further keys to strip, with the first key stripped.
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
// Search for further keys to strip, with the first key not stripped.
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
}
}
}
private static class FormatterHolder {
private final Class<?> registerdType;
private final FieldFormatter<?> formatter;
private FormatterHolder(Class<?> registerdType, FieldFormatter<?> formatter) {
this.registerdType = registerdType;
this.formatter = formatter;
}
@SuppressWarnings("unchecked")
private <T> FieldFormatter<T> getFormatter(final Class<T> requiredType) {
if(registerdType.isAssignableFrom(requiredType)) {
return (FieldFormatter<T>)formatter;
} else {
return null;
}
}
}
}