/*
* 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.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <code>Generics</code>を扱うためのユーティリティクラス。
*/
public class GenericsUtil {
/**
* ログクラス。
*/
private static final Log log = LogFactory.getLog(GenericsUtil.class);
/**
* クラスの型パラメータの実際の型を取得する。
* <p>
* <h5>実際の型の取得の可否</h5> このクラスで実際の型を取得できるのは、クラス宣言で実際の型が 指定されている場合である。 クラス宣言で実際の型が指定されていない場合、 クラス宣言で<code>WildCardType</code>が
* 指定されている場合、および、コード中で変数宣言の際に実際の型が 指定されている場合は取得できない。
* <ul>
* <li>取得できる例(子クラスので具体クラスが指定されている) <code><pre>
* public class Descendant extends Generic<String, Integer> {
* ...
* }
* </pre></code> この場合、正しく[<code>String</code>, <code>Integer</code>]の 配列が取得できる</li>
* <li>取得できる例(子クラスのクラス宣言で配列が指定されている) <code><pre>
* public class Descendant extends Generic<String[], Integer> {
* ...
* }
* </pre></code> この場合、正しく[<code>String[]</code>, <code>Integer</code>]の 配列が取得できる</li>
* <li>取得できる例(子クラスのクラス宣言で型パラメータを持つ具体クラスが指定されている) <code><pre>
* public class Descendant
* extends Generic<String[], Map<String, Object>> {
* ...
* }
* </pre></code> この場合、正しく[<code>String[]</code>, <code>Map</code>]の 配列が取得できる</li>
* <li>取得できない例(子クラスのクラス宣言で型パラメータが指定されている) <code><pre>
* public class Descendant<P, Q> extends Generic<S, T> {
* ...
* }
* </pre></code></li>
* <li>取得できない例(子クラスのクラス宣言でワイルドカードが指定されている) <code><pre>
* public class Descendant<P extends String, Q super Bean>
* extends Generic<S, T> {
* ...
* }
* </pre></code></li>
* <li>取得できない例(コード中で具体クラスが指定されている) <code><pre>
* Generic<String, Integer> descendant =
* new Generic<String, Integer>();
* </pre></code></li>
* </ul>
* </p>
* <p>
* <h5>多世代継承の場合</h5> <code>genericType</code>から<code>descendantClass</code>まで 多世代の継承がある場合も、実際の型を取得することができる。
* <ul>
* <li>多世代継承の例 <code><pre>
* public class Child<S, T> extends Generic<S, T> {
* ...
* }
*
* public class GrandChild<S, T> extends Child<S, T> {
* ...
* }
*
* public class Descendant extends GrandChild<String, Integer> {
* ...
* }
* </pre></code> この場合、正しく[<code>String</code>, <code>Integer</code>]の 配列が取得できる</li>
* </ul>
* </p>
* <p>
* <h5>型パラメータの順番が変更された場合</h5> <code>genericType</code>から<code>descendantClass</code>までの 継承の過程で型パラメータの順番が入れ替わった場合でも、
* <code>genercClass</code>で宣言された順番で実際の型を取得できる。
* <ul>
* <li>順番が入れ替わる場合の例 <code><pre>
* public class Generic<S, T> {
* ...
* }
*
* public class Child<A, T, B, S> extends Generic<S, T> {
* ...
* }
*
* public class Descendant
* extends Generic<Boolean, Integer, Double, String> {
* ...
* }
* </pre></code> この場合、正しく[<code>String</code>, <code>Integer</code>]の 配列が取得できる</li>
* </ul>
* </p>
* @param <T> 型パラメータ宣言をしたクラスの型。
* @param genericClass 型パラメータ宣言をしたクラス。
* @param descendantClass <code>genericsClass</code>を継承し、 具体的な型パラメータを指定したクラス。
* @return 実際の型を表す<code>Class</code>インスタンスの配列。 順番は<code>genercClass</code>で宣言された順番である。
* @throws IllegalArgumentException 引数<code>genericClass</code>が <code>null</code>の場合。 引数<code>descendantClass</code>が
* <code>null</code>の場合。
* @throws IllegalStateException <code>descendantClass</code>の実装で型パラメータが 具体クラスとして指定されていない場合。 引数<code>genercClass</code>
* が型パラメータを宣言したクラスでは なかった場合。
*/
@SuppressWarnings("rawtypes")
public static <T> Class[] resolveParameterizedClass(Class<T> genericClass,
Class<? extends T> descendantClass) throws IllegalArgumentException, IllegalStateException {
if (genericClass == null) {
throw new IllegalArgumentException("Argument 'genericsClass' ("
+ Class.class.getName() + ") is null");
}
if (descendantClass == null) {
throw new IllegalArgumentException("Argument 'descendantClass'("
+ Class.class.getName() + ") is null");
}
List<ParameterizedType> ancestorTypeList = getAncestorTypeList(
genericClass, descendantClass);
ParameterizedType parameterizedType = ancestorTypeList.get(
ancestorTypeList.size() - 1);
// parameterizedTypeの実際の型引数を表す Type オブジェクトの配列
// 例:AbstractBLogic<P, R>の型引数はPとR。
Type[] actualTypes = parameterizedType.getActualTypeArguments();
// インスタンスで宣言された型パラメータを実際の型に解決する。
Class[] actualClasses = new Class[actualTypes.length];
for (int i = 0; i < actualTypes.length; i++) {
// actualTypes[i]→i番目の型引数。
// ancestorList→型パラメータ宣言しているクラスのリスト。
actualClasses[i] = resolveTypeVariable(actualTypes[i],
ancestorTypeList);
}
return actualClasses;
}
/**
* 型パラメータの実際の型を取得する。
* <p>
* <h5>実際の型の取得の可否</h5> このクラスで実際の型を取得できるのは、クラス宣言で実際の型が 指定されている場合である。 クラス宣言で実際の型が指定されていない場合、 クラス宣言で<code>WildCardType</code>が
* 指定されている場合、および、コード中で変数宣言の際に実際の型が 指定されている場合は取得できない。
* <ul>
* <li>取得できる例(子クラスので具体クラスが指定されている) <code><pre>
* public class Descendant extends Generic<String, Integer> {
* ...
* }
* </pre></code> この場合、正しく<code>String</code>または<code>Integer</code>が 取得できる</li>
* <li>取得できる例(子クラスのクラス宣言で配列が指定されている) <code><pre>
* public class Descendant extends Generic<String[], Integer> {
* ...
* }
* </pre></code> この場合、正しく<code>String[]</code>または<code>Integer</code>が 取得できる</li>
* <li>取得できる例(子クラスのクラス宣言で型パラメータを持つ具体クラスが指定されている) <code><pre>
* public class Descendant
* extends Generic<String[], Map<String, Object>> {
* ...
* }
* </pre></code> この場合、正しく<code>String[]</code>または<code>Map</code> が取得できる</li>
* <li>取得できない例(子クラスのクラス宣言で型パラメータが指定されている) <code><pre>
* public class Descendant<P, Q> extends Generic<S, T> {
* ...
* }
* </pre></code></li>
* <li>取得できない例(子クラスのクラス宣言でワイルドカードが指定されている) <code><pre>
* public class Descendant<P extends String, Q super Bean>
* extends Generic<S, T> {
* ...
* }
* </pre></code></li>
* <li>取得できない例(コード中で具体クラスが指定されている) <code><pre>
* Generic<String, Integer> descendant =
* new Generic<String, Integer>();
* </pre></code></li>
* </ul>
* </p>
* <p>
* <h5>多世代継承の場合</h5> <code>genericType</code>から<code>descendantClass</code>まで 多世代の継承がある場合も、実際の型を取得することができる。
* <ul>
* <li>多世代継承の例 <code><pre>
* public class Child<S, T> extends Generic<S, T> {
* ...
* }
*
* public class GrandChild<S, T> extends Child<S, T> {
* ...
* }
*
* public class Descendant extends GrandChild<String, Integer> {
* ...
* }
* </pre></code> この場合、正しく<code>String</code>または<code>Integer</code> が取得できる</li>
* </ul>
* </p>
* <p>
* <h5>型パラメータの順番が変更された場合</h5> <code>genericType</code>から<code>descendantClass</code>までの 継承の過程で型パラメータの順番が入れ替わった場合でも、
* <code>genercClass</code>で宣言された順番で実際の型を取得できる。
* <ul>
* <li>順番が入れ替わる場合の例 <code><pre>
* public class Generic<S, T> {
* ...
* }
*
* public class Child<A, T, B, S> extends Generic<S, T> {
* ...
* }
*
* public class Descendant
* extends Generic<Boolean, Integer, Double, String> {
* ...
* }
* </pre></code> この場合、正しく<code>String</code>または<code>Integer</code> が取得できる</li>
* </ul>
* </p>
* @param <T> 型パラメータ宣言をしたクラスの型。
* @param genericClass 型パラメータ宣言をしたクラス。
* @param descendantClass <code>genericsClass</code>を継承し、 具体的な型パラメータを指定したクラス。
* @param index 実際の型を取得する型パラメータの宣言順序。
* @return 実際の型を表す<code>Class</code>インスタンス。
* @throws IllegalArgumentException 引数<code>genericClass</code>が <code>null</code>の場合。 引数<code>descendantClass</code>が
* <code>null</code>の場合。 引数<code>index</code>が<code>0</code>より小さい、または、 宣言された型パラメータ数以上の場合。
* @throws IllegalStateException <code>descendantClass</code>の実装で型パラメータが 具体クラスとして指定されていない場合。 引数<code>genercClass</code>
* が型パラメータを宣言したクラスでは なかった場合。
*/
public static <T> Class<? extends Object> resolveParameterizedClass(
Class<T> genericClass, Class<? extends T> descendantClass,
int index) throws IllegalArgumentException, IllegalStateException {
if (genericClass == null) {
throw new IllegalArgumentException("Argument 'genericsClass' ("
+ Class.class.getName() + ") is null");
}
if (descendantClass == null) {
throw new IllegalArgumentException("Argument 'descendantClass'("
+ Class.class.getName() + ") is null");
}
List<ParameterizedType> ancestorTypeList = getAncestorTypeList(
genericClass, descendantClass);
ParameterizedType parameterizedType = ancestorTypeList.get(
ancestorTypeList.size() - 1);
// parameterizedTypeの実際の型引数を表す Type オブジェクトの配列
// 例:AbstractBLogic<P, R>の型引数はPとR。
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");
}
// actualTypes[index]→index番目の型引数。
// ancestorList→型パラメータ宣言しているクラスのリスト。
return resolveTypeVariable(actualTypes[index], ancestorTypeList);
}
/**
* 特定の型から型パラメータを宣言したクラスまでの <code>ParameterizedType</code>のリストを取得する。
* @param <T> 型パラメータ宣言をしたクラスの型。
* @param genericClass 型パラメータ宣言をしたクラス。
* @param descendantClass <code>genericsClass</code>を継承し、 具体的な型パラメータを指定したクラス。
* @return 特定の型から型パラメータを宣言したクラスまでの <code>ParameterizedType</code>のリスト。
* @throws IllegalStateException <code>descendantClass</code>の 実装で型パラメータが具体クラスとして指定されていない場合。 引数<code>genercClass</code>
* が型パラメータを宣言したクラスでは なかった場合。
*/
protected static <T> List<ParameterizedType> getAncestorTypeList(
Class<T> genericClass,
Class<? extends T> descendantClass) throws IllegalStateException {
List<ParameterizedType> ancestorTypeList = new ArrayList<ParameterizedType>();
Class<?> clazz = descendantClass;
boolean isInterface = genericClass.isInterface();
while (clazz != null) {
Type type = clazz.getGenericSuperclass();
if (checkParameterizedType(type, genericClass, ancestorTypeList)) {
break;
}
// 型パラメータを宣言したクラスがインタフェースの場合、
// インタフェースについてもチェックを行う。
if (!isInterface) {
clazz = clazz.getSuperclass();
continue;
}
if (checkInterfaceAncestors(genericClass, ancestorTypeList,
clazz)) {
break;
}
// クラスのインタフェース内に、指定したインタフェースが存在しなかった
// 場合に備えて、親クラスをチェック対象にする。
// 現状この箇所を通過することはないと思われる。
// 念のため、チェックを残しておく。
// 理由は、GenericsのリフレクションAPIについては実装が不定であるためである。
// 問題がある場合は、削除すること。
clazz = clazz.getSuperclass();
}
// 型パラメータ宣言しているクラスのインスタンスを取得。
// 例:AbstractBLogic<P, R>クラス。
if (ancestorTypeList.isEmpty()) {
throw new IllegalStateException("Argument 'genericClass'("
+ genericClass.getName()
+ ") does not declare type parameter");
}
// この箇所のチェックで例外が出る場合はないと思われるが、
// 念のため、チェックを残しておく。
// 理由は、GenericsのリフレクションAPIについては実装が不定であるためである。
// 問題がある場合は、削除すること。
ParameterizedType targetType = ancestorTypeList.get(ancestorTypeList
.size() - 1);
if (!targetType.getRawType().equals(genericClass)) {
throw new IllegalStateException("Class(" + descendantClass.getName()
+ ") is not concrete class of Class(" + genericClass
.getName() + ")");
}
return ancestorTypeList;
}
/**
* インタフェース型から型パラメータを宣言したクラスまでの <code>ParameterizedType</code>のリストを取得する。
* @param <T> 型パラメータを宣言したクラスの型。
* @param genericClass 型パラメータを宣言したクラス。
* @param ancestorTypeList <code>ParameterizedType</code>を 追加するリスト。
* @param clazz 検査対象のインタフェース型。
* @return 型パラメータを宣言したクラスが見つかった場合は<code>true</code>。 見つからなかった場合は<code>false</code>。
*/
protected static <T> boolean checkInterfaceAncestors(Class<T> genericClass,
List<ParameterizedType> ancestorTypeList, Class<?> clazz) {
boolean genericTypeFound = false;
Type[] interfaceTypes = clazz.getGenericInterfaces();
for (Type interfaceType : interfaceTypes) {
genericTypeFound = checkParameterizedType(interfaceType,
genericClass, ancestorTypeList);
if (genericTypeFound) {
return true;
}
@SuppressWarnings("rawtypes")
Class[] interfaces = clazz.getInterfaces();
for (Class<?> interfaceClass : interfaces) {
if (checkInterfaceAncestors(genericClass, ancestorTypeList,
interfaceClass)) {
return true;
}
}
}
return false;
}
/**
* <code>Type</code>型をチェックし、<code>ParameterizedType</code> かつ、型パラメータを宣言したクラスのサブクラスである場合、リストに追加する。
* @param <T> 型パラメータを宣言したクラスの型。
* @param type 検査対象の型。
* @param genericClass 型パラメータを宣言したクラス。
* @param ancestorTypeList <code>ParameterizedType</code>を 追加するリスト。
* @return <code>type</code>が型パラメータを宣言したクラスと同じクラスの場合。
*/
protected static <T> boolean checkParameterizedType(Type type,
Class<T> genericClass, List<ParameterizedType> ancestorTypeList) {
if (!(type instanceof ParameterizedType)) {
return false;
}
// ParameterizedTypeのインスタンスである場合、ParameterizedType
// にキャストする。
ParameterizedType parameterlizedType = (ParameterizedType) type;
// インタフェースのGenericsの場合、異なるParameterizedTypeが
// 渡されるかもしれないのでチェック。
// ただし、現状では異なるものが渡されることはないと思われる。
// 念のため、チェックを残しておく。
// 理由は、GenericsのリフレクションAPIについては実装が不定であるためである。
// 問題がある場合は、削除すること。
if (!genericClass.isAssignableFrom((Class<?>) parameterlizedType
.getRawType())) {
return false;
}
ancestorTypeList.add(parameterlizedType);
// #getRawType型パラメータ宣言のないTypeを取得する。
if (parameterlizedType.getRawType().equals(genericClass)) {
return true;
}
return false;
}
/**
* 型パラメータの具体的な<code>Type</code>を取得する。
* @param type 解決する必要のある<code>Type</code>インスタンス。
* @param ancestorTypeList <code>type</code>の具体的な型が 宣言されている可能性のある<code>ParameterizedType</code>のリスト。
* @return 実行時の型変数。
* @throws IllegalStateException 引数<code>type</code>が <code>Class</code>型、および、 <code>TypeVariable</code>型ではない場合。 引数
* <code>type</code>がメソッド、 または、コンストラクタで宣言されている場合。 引数<code>type</code>の実際の型が<code>Class</code>ではない
* (ワイルドカード、配列)場合。
*/
protected static Class<? extends Object> resolveTypeVariable(Type type,
List<ParameterizedType> ancestorTypeList) throws IllegalStateException {
if (isNotTypeVariable(type)) {
return getRawClass(type);
}
// TypeVariable:型変数を定義するインタフェース。
TypeVariable<?> targetType = (TypeVariable<?>) type;
Type actualType = null;
for (int i = ancestorTypeList.size() - 1; i >= 0; i--) {
ParameterizedType ancestorType = ancestorTypeList.get(i);
// 型パラメータが宣言されているクラスを取得
GenericDeclaration declaration = targetType.getGenericDeclaration();
if (!(declaration instanceof Class)) {
throw new IllegalStateException("TypeVariable(" + targetType
.getName() + " is not declared at Class "
+ "(ie. is declared at Method or Constructor)");
}
// 祖先クラスがGenericsの宣言元でない場合は飛ばす。
Class<?> declaredClass = (Class<?>) declaration;
if (declaredClass != ancestorType.getRawType()) {
continue;
}
// 型パラメータの宣言順序を解決して、代入された型引数を取得。
// 例:ConcreteAbstractBLogic<R,P> extends AbstractBLogic<P,R>
// のような場合に正しくtypeに対応するパラメータを取り出す。
Type[] parameterTypes = declaredClass.getTypeParameters();
int index = ArrayUtils.indexOf(parameterTypes, targetType);
if (index == -1) {
// この箇所のチェックで例外が出る場合はないと思われるが、
// 念のため、チェックを残しておく。
// 理由は、GenericsのリフレクションAPIについては実装が不定であるためである。
// 問題がある場合は、削除すること。
throw new IllegalStateException("Class(" + declaredClass
.getName() + ") does not declare TypeValidable("
+ targetType.getName() + ")");
}
actualType = ancestorType.getActualTypeArguments()[index];
if (log.isDebugEnabled()) {
log.debug("resolved " + targetType.getName() + " -> "
+ actualType);
}
if (isNotTypeVariable(actualType)) {
return getRawClass(actualType);
}
targetType = (TypeVariable<?>) actualType;
}
throw new IllegalStateException("Concrete type of Type(" + type
+ ") was not found in ancestorList(" + ancestorTypeList + ")");
}
/**
* 引数<code>type</code>が<code>Class</code>型 であるか、<code>TypeVariable</code>型かを判定する。
* @param type <code>Type</code>インスタンス。
* @return 引数<code>type</code>が <code>Class, ParameterizedType, GenericArrayType</code>の場合 <code>true</code>。 引数
* <code>type</code>が<code>TypeVariable</code>の場合 <code>false</code>。
* @throws IllegalStateException 引数<code>type</code>が <code>Class</code>、<code>ParameterizedType</code>、
* <code>GenericArrayType</code>、<code>TypeVariable</code>の いずれでもない場合。
*/
protected static boolean isNotTypeVariable(
Type type) throws IllegalStateException {
if (type instanceof Class) {
return true;
} else if (type instanceof TypeVariable) {
return false;
} else if (type instanceof ParameterizedType) {
return true;
} else if (type instanceof GenericArrayType) {
return true;
}
throw new IllegalStateException("Type(" + type + ") is not instance of "
+ TypeVariable.class.getName() + ", " + ParameterizedType.class
.getName() + ", " + GenericArrayType.class.getName()
+ " nor " + Class.class.getName());
}
/**
* 引数<code>type</code>の実際の型を返却する。
* @param type <code>Type</code>インスタンス。
* @return <code>Class</code>インスタンス。
* @throws IllegalStateException 引数<code>type</code>が <code>Class</code>、<code>ParameterizedType</code>、
* <code>GenericArrayType</code>のいずれでもない場合。
*/
@SuppressWarnings("unchecked")
protected static Class<? extends Object> getRawClass(
Type type) throws IllegalStateException {
if (type instanceof Class) {
return (Class<? extends Object>) type;
} else if (type instanceof ParameterizedType) {
return getRawClass(((ParameterizedType) type).getRawType());
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type)
.getGenericComponentType();
Class<? extends Object> componentClass = getRawClass(componentType);
return Array.newInstance(componentClass, 0).getClass();
}
throw new IllegalStateException("Type(" + type + ") is not instance of "
+ ParameterizedType.class.getName() + ", "
+ GenericArrayType.class.getName() + " nor " + Class.class
.getName());
}
}