PropertyTypeNavigator.java
package com.gh.mygreen.xlsmapper.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.gh.mygreen.xlsmapper.util.PropertyPath.Token;
/**
* クラス定義からプロパティのクラスタイプを取得する。
* <p>プロパティは式言語の形式に似た形式をとることが可能で、フィールドにもアクセスできます。</p>
*
*
* @since 2.0
* @author T.TSUCHIE
*
*/
public class PropertyTypeNavigator {
/**
* 非公開のプロパティにアクセス可能かどうか。
*/
private boolean allowPrivate;
/**
* クラスタイプの解析ができない場合に処理を終了するかどうか。
*/
private boolean ignoreNotResolveType;
/**
* プロパティの解析結果をキャッシュするかどうか。
*/
private boolean cacheWithPath;
/**
* プロパティの式を解析して、トークンに分解するクラス。
*/
private final PropertyPathTokenizer tokenizer = new PropertyPathTokenizer();
/**
* プロパティの解析結果をキャッシュデータ
*/
private final Map<String, PropertyPath> cacheData = new ConcurrentHashMap<>();
/**
* プロパティの値を取得する。
* <p>オプションはデフォルト値で処理する。</p>
* @param rootClass 取得元となるクラス
* @param property プロパティの式。
* @return プロパティのクラスタイプ。
* @throws IllegalArgumentException peropety is null or empty.
* @throws PropertyAccessException 存在しないプロパティを指定した場合など。
* @throws IllegalStateException リストやマップにアクセスする際にGenericsタイプが設定されておらずクラスタイプが取得できない場合。
* ただし、オプションignoreNotResolveType = falseのとき。
*/
public static Object get(final Class<?> rootClass, final String property) {
return new PropertyTypeNavigator().getPropertyType(rootClass, property);
}
/**
* デフォルトコンストラクタ
*/
public PropertyTypeNavigator() {
}
/**
* プロパティの値を取得する。
*
* @param rootClass 取得元となるクラス
* @param property プロパティの式。
* @return プロパティのクラスタイプ。
* @throws IllegalArgumentException peropety is null or empty.
* @throws PropertyAccessException 存在しないプロパティを指定した場合など。
* @throws IllegalStateException リストやマップにアクセスする際にGenericsタイプが設定されておらずクラスタイプが取得できない場合。
* ただし、オプションignoreNotResolveType = falseのとき。
*/
public Class<?> getPropertyType(final Class<?> rootClass, final String property) {
ArgUtils.notEmpty(property, "property");
final PropertyPath path = parseProperty(property);
final LinkedList<Object> stack = new LinkedList<>();
Class<?> targetClass = rootClass;
for(Token token : path.getPathAsToken()) {
targetClass = accessProperty(targetClass, token, stack);
if(targetClass == null) {
return null;
}
}
return targetClass;
}
/**
* プロパティの式をパースして、{@link PropertyPath} オブジェクトに変換する。
* @param property プロパティアクセス用の式
* @return 式を解析した結果 {@link PropertyPath}
*/
private PropertyPath parseProperty(final String property) {
if(isCacheWithPath()) {
return cacheData.computeIfAbsent(property, k -> tokenizer.parse(k));
} else {
return tokenizer.parse(property);
}
}
private Class<?> accessProperty(final Class<?> targetClass, final Token token, final LinkedList<Object> stack) {
if(token instanceof Token.Separator) {
return accessPropertyBySeparator(targetClass, (Token.Separator)token, stack);
} else if(token instanceof Token.Name) {
return accessPropertyByName(targetClass, (Token.Name)token, stack);
} else if(token instanceof Token.Key) {
return accessPropertyByKey(targetClass, (Token.Key)token, stack);
}
throw new IllegalStateException("not support token type : " + token.getValue());
}
private Class<?> accessPropertyBySeparator(final Class<?> targetClass, final Token.Separator token, final LinkedList<Object> stack) {
return targetClass;
}
private Class<?> accessPropertyByName(final Class<?> targetClass, final Token.Name token, final LinkedList<Object> stack) {
// メソッドアクセス
final String getterMethodName = "get" + Utils.capitalize(token.getValue());
try {
Method getterMethod = allowPrivate ?
targetClass.getDeclaredMethod(getterMethodName) : targetClass.getMethod(getterMethodName);
getterMethod.setAccessible(true);
// Collectionなどの場合、メソッド情報をスタックに積んでおく
stack.push(getterMethod);
return getterMethod.getReturnType();
} catch (NoSuchMethodException | SecurityException e) {
// not found method
}
// boolean用メソッドアクセス
final String booleanMethodName = "is" + Utils.capitalize(token.getValue());
try {
Method getterMethod = allowPrivate ?
targetClass.getDeclaredMethod(booleanMethodName) : targetClass.getMethod(booleanMethodName);;
getterMethod.setAccessible(true);
return getterMethod.getReturnType();
} catch (NoSuchMethodException | SecurityException e) {
// not found method
}
// フィールドアクセス
final String fieldName = token.getValue();
try {
Field field = allowPrivate ?
targetClass.getDeclaredField(fieldName) : targetClass.getField(fieldName);
field.setAccessible(true);
// Collectionなどの場合、メソッド情報をスタックに積んでおく
stack.push(field);
return field.getType();
} catch (NoSuchFieldException | SecurityException e) {
// not found field
}
throw new PropertyAccessException("not found property : " + token.getValue());
}
private Class<?> accessPropertyByKey(final Class<?> targetClass, final Token.Key token, final LinkedList<Object> stack) {
final Object parent = stack.isEmpty() ? null : stack.pollFirst();
if(parent == null) {
return null;
}
if(Collection.class.isAssignableFrom(targetClass)) {
Type argType = null;
if(parent instanceof Method) {
// getterメソッドのからGenericsのタイプを取得する
Type type = ((Method)parent).getGenericReturnType();
if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
if(ignoreNotResolveType) {
return null;
} else {
throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
}
}
argType = ((ParameterizedType)type).getActualTypeArguments()[0];
} else if(parent instanceof Field) {
Type type = ((Field)parent).getGenericType();
if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
if(ignoreNotResolveType) {
return null;
} else {
throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
}
}
argType = ((ParameterizedType)type).getActualTypeArguments()[0];
} else if(parent instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) parent;
argType = type.getActualTypeArguments()[0];
} else {
if(ignoreNotResolveType) {
return null;
} else {
throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
}
}
if(argType == null) {
return null;
}
if(argType instanceof Class) {
return (Class<?>)argType;
} else if(argType instanceof ParameterizedType) {
ParameterizedType paramType = ((ParameterizedType)argType);
stack.push(paramType);
return (Class<?>)paramType.getRawType();
}
} else if (targetClass.isArray()) {
return targetClass.getComponentType();
} else if(Map.class.isAssignableFrom(targetClass)) {
Type argType = null;
if(parent instanceof Method) {
// getterメソッドのからGenericsのタイプを取得する
Type type = ((Method)parent).getGenericReturnType();
if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
if(ignoreNotResolveType) {
return null;
} else {
throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
}
}
argType = ((ParameterizedType)type).getActualTypeArguments()[1];
} else if(parent instanceof Field) {
Type type = ((Field)parent).getGenericType();
if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
if(ignoreNotResolveType) {
return null;
} else {
throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
}
}
argType = ((ParameterizedType)type).getActualTypeArguments()[1];
} else if(parent instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) parent;
argType = type.getActualTypeArguments()[1];
} else {
if(ignoreNotResolveType) {
return null;
} else {
throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
}
}
if(argType == null) {
return null;
}
if(argType instanceof Class) {
return (Class<?>)argType;
} else if(argType instanceof ParameterizedType) {
ParameterizedType paramType = ((ParameterizedType)argType);
stack.push(paramType);
return (Class<?>)paramType.getRawType();
}
}
throw new PropertyAccessException("not support key access : " + targetClass.getName());
}
/**
* 今までのキャッシュデータをクリアする。
*/
public void clearCache() {
this.cacheData.clear();
}
/**
* 実行時オプション - 非公開のプロパティにアクセス可能かどうか。
* @return true: アクセスを許可する。false: デフォルト値。
*/
public boolean isAllowPrivate() {
return allowPrivate;
}
/**
* 実行時オプション - 非公開のプロパティにアクセス可能かどうか。
* @param allowPrivate true: アクセスを許可する。
*/
public void setAllowPrivate(boolean allowPrivate) {
this.allowPrivate = allowPrivate;
}
/**
* クラスタイプの解析ができない場合に処理を終了するかどうか。
* <p>ListなどでGenericsタイプが指定されていでクラスタイプが取得できないときに、nullを返すかどうか指定します。</p>
* @return true:その時点で処理を終了する。
*/
public boolean isIgnoreNotResolveType() {
return ignoreNotResolveType;
}
/**
* クラスタイプの解析ができない場合に処理を終了するかどうか。
* <p>ListなどでGenericsタイプが指定されていでクラスタイプが取得できないときに、nullを返すかどうか指定します。</p>
* @param ignoreNotResolveType true:その時点で処理を終了する。
*/
public void setIgnoreNotResolveType(boolean ignoreNotResolveType) {
this.ignoreNotResolveType = ignoreNotResolveType;
}
/**
* プロパティの解析結果をキャッシュするかどうか。
* @return true: キャッシュする。false: デフォルト値。
*/
public boolean isCacheWithPath() {
return cacheWithPath;
}
/**
* プロパティの解析結果をキャッシュするかどうか。
* @param cacheWithPath true: キャッシュする。
*/
public void setCacheWithPath(boolean cacheWithPath) {
this.cacheWithPath = cacheWithPath;
}
}