/**
* The MIT License
*
* Copyright (C) 2007 Asterios Raptis
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package de.alpharogroup.lang;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.alpharogroup.check.Check;
/**
* The Class TypeArgumentsUtils is a utility class for getting the type arguments a child class has
* used to extend a generic base class. It is inspired from the article Reflecting generics by Ian
* Robertson at <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=208860"
* >http://www.artima.com/weblogs/viewpost.jsp?thread=208860</a>. In the comments someone asked if
* we are allowed to use the source code from the article. The answer of Ian Robertson is:
* Absolutely, you may use this code. "Consider it open sourced".
*
*/
public class TypeArgumentsUtils
{
/**
* Get the underlying class for a type, or null if the type is a variable type.
*
* @param type
* the type
* @return the underlying class
*/
private static Class<?> getClass(final Type type)
{
if (type instanceof Class)
{
return (Class<?>)type;
}
else if (type instanceof ParameterizedType)
{
return getClass(((ParameterizedType)type).getRawType());
}
else if (type instanceof GenericArrayType)
{
final Type componentType = ((GenericArrayType)type).getGenericComponentType();
final Class<?> componentClass = getClass(componentType);
if (componentClass != null)
{
return Array.newInstance(componentClass, 0).getClass();
}
else
{
return null;
}
}
else
{
return null;
}
}
/**
* Gets the first type argument from the childClass.
*
* @param <T>
* the generic type of the baseClass
* @param baseClass
* the base class
* @param childClass
* the child class
* @return the first type argument
*/
public static <T> Class<?> getFirstTypeArgument(final Class<T> baseClass,
final Class<? extends T> childClass)
{
return getTypeArgument(baseClass, childClass, 0);
}
/**
* Gets the type argument from the childClass at the given index or null if it does not exists.
*
* @param <T>
* the generic type of the baseClass
* @param baseClass
* the base class
* @param childClass
* the child class
* @param index
* the index of the type argument
* @return the type argument from the childClass at the given index or null if it does not
* exists.
*/
public static <T> Class<?> getTypeArgument(final Class<T> baseClass,
final Class<? extends T> childClass, final int index)
{
final List<Class<?>> typeArguments = getTypeArguments(baseClass, childClass);
if (typeArguments != null && !typeArguments.isEmpty() && index < typeArguments.size())
{
return typeArguments.get(index);
}
return null;
}
/**
* Get the actual type arguments a child class has used to extend a generic base class.
*
* @param <T>
* the generic type of the baseClass
* @param baseClass
* the base class
* @param childClass
* the child class
* @return a list of the raw classes for the actual type arguments.
*/
public static <T> List<Class<?>> getTypeArguments(final Class<T> baseClass,
final Class<? extends T> childClass)
{
Check.get().notNull(baseClass, "baseClass").notNull(childClass, "childClass");
final Map<Type, Type> resolvedTypes = new HashMap<>();
Type type = childClass;
// start walking up the inheritance hierarchy until we hit baseClass
while (!getClass(type).equals(baseClass))
{
if (type instanceof Class)
{
// there is no useful information for us in raw types, so just
// keep going.
type = ((Class<?>)type).getGenericSuperclass();
}
else
{
final ParameterizedType parameterizedType = (ParameterizedType)type;
final Class<?> rawType = (Class<?>)parameterizedType.getRawType();
resolvedTypes.putAll(getTypeArgumentsAndParameters(type));
if (!rawType.equals(baseClass))
{
type = rawType.getGenericSuperclass();
}
}
}
// finally, for each actual type argument provided to baseClass,
// determine (if possible)
// the raw class for that type argument.
Type[] actualTypeArguments;
if (type instanceof Class)
{
actualTypeArguments = ((Class<?>)type).getTypeParameters();
}
else
{
actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
}
final List<Class<?>> typeArgumentsAsClasses = new ArrayList<>();
// resolve types by chasing down type variables.
for (Type baseType : actualTypeArguments)
{
while (resolvedTypes.containsKey(baseType))
{
baseType = resolvedTypes.get(baseType);
}
typeArgumentsAsClasses.add(getClass(baseType));
}
return typeArgumentsAsClasses;
}
/**
* Gets the type arguments and parameters.
*
* @param type
* the type
* @return the type arguments and parameters
*/
private static Map<Type, Type> getTypeArgumentsAndParameters(final Type type)
{
final ParameterizedType parameterizedType = (ParameterizedType)type;
final Class<?> rawType = (Class<?>)parameterizedType.getRawType();
final Map<Type, Type> resolvedTypes = new HashMap<>();
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
for (int i = 0; i < actualTypeArguments.length; i++)
{
resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
}
return resolvedTypes;
}
}