/*
* Copyright (c) 2007 NTT DATA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jp.terasoluna.fw.util;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <code>JavaBean</code>のプロパティの <code>Generics</code>を扱うためのユーティリティクラス。
*/
public class GenericPropertyUtil {
/**
* ログクラス。
*/
private static final Log log = LogFactory.getLog(GenericPropertyUtil.class);
/**
* <code>JavaBean</code>の <code>Collection</code>型プロパティの要素の型を取得する。
* <p>
* <h5>取得例</h5>
*
* <pre>
* <code>
* public class Bean {
* private List<String> list;
* public List<String> getList() {
* return this.list;
* }
* }
* </code>
* </pre>
*
* 上記のような<code>Bean</code>に対して、以下のように使用すると、 String.classを取得できる。
*
* <pre>
* <code>
* Bean bean = new Bean();
* Class elementType =
* GenericCollectionUtil.resolveCollectionType(
* bean, "list");
* </code>
* </pre>
*
* @param bean <code>JavaBean</code>インスタンス。
* @param name <code>Collection</code>型プロパティの名前。
* @return <code>Collection</code>の要素の型。 特定できない場合は<code>Object</code>型が返却される。
* @throws IllegalArgumentException 引数<code>bean</code>が <code>null</code>の場合。引数<code>name</code>が <code>null</code>
* 、空文字、空白文字列の場合。 <code>JavaBean</code>のプロパティの 取得メソッドを取得できなかった場合
* @throws IllegalStateException 指定されたプロパティが<code>Collection</code>実装クラス ではない場合。
*/
public static Class<?> resolveCollectionType(Object bean,
String name) throws IllegalArgumentException, IllegalStateException {
return resolveType(bean, name, Collection.class, 0);
}
/**
* <code>JavaBean</code>の <code>Generics</code>型プロパティで指定された型を取得する。
* <p>
* <h5>取得例</h5>
*
* <pre>
* <code>
* public class Bean {
* private Map<String, Boolean> map;
* public Map<String, Boolean> getMap() {
* return this.map;
* }
* }
* </code>
* </pre>
*
* 上記のような<code>Bean</code>に対して、以下のように使用すると、 String.classを取得できる。
*
* <pre>
* <code>
* Bean bean = new Bean();
* Class keyType =
* GenericCollectionUtil.resolveType(
* bean, "map", Map.class, 0);
* </code>
* </pre>
*
* @param bean <code>JavaBean</code>インスタンス。
* @param name <code>Generics</code>型プロパティの名前。
* @param genericClass <code>Generics</code>型プロパティの 型定義を行っているクラス。
* @param index 型パラメータの宣言順序。
* @return <code>Generics</code>型プロパティで指定された型。 特定できない場合は<code>Object</code>型が返却される。
* @throws IllegalArgumentException 引数<code>bean</code>が <code>null</code>の場合。引数<code>name</code>が <code>null</code>
* 、空文字、空白文字列の場合。 引数<code>genericClass</code>が<code>null</code>の場合。 引数<code>index</code>が<code>0</code>
* より小さい、または、 宣言された型パラメータ数以上の場合。 <code>JavaBean</code>のプロパティの 取得メソッドを取得できなかった場合
* @throws IllegalStateException 型パラメータが<code>WildCardType</code>である場合。
*/
public static Class<?> resolveType(Object bean, String name,
Class<?> genericClass,
int index) throws IllegalArgumentException, IllegalStateException {
if (bean == null) {
throw new IllegalArgumentException("Argument 'bean' ("
+ Object.class.getName() + " is null");
}
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("Argument 'name' ("
+ String.class.getName() + " is empty");
}
Method method = getMethod(bean, name);
return resolveType(genericClass, method.getReturnType(), method
.getGenericReturnType(), index);
}
/**
* <code>JavaBean</code>のプロパティの取得メソッドを 取得する。
* @param bean <code>JavaBean</code>インスタンス。
* @param name <code>Generics</code>型プロパティの名前。
* @return <code>JavaBean</code>に定義されたプロパティの取得メソッド。
* @throws IllegalArgumentException <code>JavaBean</code>のプロパティの 取得メソッドを取得できなかった場合。
*/
protected static Method getMethod(Object bean,
String name) throws IllegalArgumentException {
PropertyDescriptor descriptor = null;
try {
descriptor = PropertyUtils.getPropertyDescriptor(bean, name);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Failed to detect getter for "
+ bean.getClass().getName() + "#" + name, e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException("Failed to detect getter for "
+ bean.getClass().getName() + "#" + name, e);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Failed to detect getter for "
+ bean.getClass().getName() + "#" + name, e);
}
Method method = null;
if (descriptor != null) {
method = descriptor.getReadMethod();
}
if (method == null) {
throw new IllegalArgumentException(bean.getClass().getName()
+ " has no getter for property " + name);
}
return method;
}
/**
* フィールド、または、メソッドの情報を元に <code>Generics</code>型で指定された型を取得する。
* @param genericClass <code>Generics</code>型プロパティの 型定義を行っているクラス。
* @param clazz 具体的な型パラメータを指定したクラス。
* @param type 具体的な型パラメータを指定したクラスのインスタンスの <code>Type</code>インスタンス。
* @param index 型パラメータの宣言順序。
* @return <code>Generics</code>型で指定された型。 特定できない場合は<code>Object</code>型が返却される。
* @throws IllegalArgumentException 引数<code>genericClass</code>が <code>null</code>の場合。 引数<code>clazz</code>が
* <code>null</code>の場合。 引数<code>index</code>が<code>0</code>より小さい、または、 宣言された型パラメータ数以上の場合。
* @throws IllegalStateException 型パラメータが<code>WildCardType</code>である場合。
*/
@SuppressWarnings("unchecked")
protected static Class<?> resolveType(Class<?> genericClass, @SuppressWarnings("rawtypes") Class clazz,
Type type,
int index) throws IllegalArgumentException, IllegalStateException {
if (genericClass == null) {
throw new IllegalArgumentException("Argument 'genericsClass' ("
+ Class.class.getName() + ") is null");
}
if (clazz == null || !genericClass.isAssignableFrom(clazz)) {
throw new IllegalStateException(genericClass
+ " is not assignable from " + clazz);
}
List<ParameterizedType> ancestorTypeList = null;
try {
ancestorTypeList = GenericsUtil.getAncestorTypeList(genericClass,
clazz);
} catch (IllegalStateException e) {
if (log.isTraceEnabled()) {
log.trace(e.getMessage());
}
}
if (ancestorTypeList == null) {
ancestorTypeList = new ArrayList<ParameterizedType>();
}
if (type instanceof ParameterizedType) {
ancestorTypeList.add(0, (ParameterizedType) type);
}
if (ancestorTypeList.size() <= 0) {
throw new IllegalStateException("No parameterizedType was detected.");
}
ParameterizedType parameterizedType = ancestorTypeList.get(
ancestorTypeList.size() - 1);
Type[] actualTypes = parameterizedType.getActualTypeArguments();
// インスタンスで宣言された型パラメータを実際の型に解決する。
if (index < 0 || index >= actualTypes.length) {
throw new IllegalArgumentException("Argument 'index'(" + Integer
.toString(index) + ") is out of bounds of"
+ " generics parameters");
}
Class<?> resolved = Object.class;
try {
resolved = GenericsUtil.resolveTypeVariable(actualTypes[index],
ancestorTypeList);
} catch (IllegalStateException e) {
if (log.isTraceEnabled()) {
log.trace(e.getMessage());
}
}
return resolved;
}
}