/* * 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; } }