/*
* JBoss, Home of Professional Open Source
* Copyright 2014, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 org.jboss.weld.resolution;
import static org.jboss.weld.util.Types.boxedClass;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import org.jboss.weld.logging.ReflectionLogger;
import org.jboss.weld.util.Types;
import org.jboss.weld.util.reflection.HierarchyDiscovery;
import org.jboss.weld.util.reflection.Reflections;
/**
* Utility class that captures standard covariant Java assignability rules.
*
* This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, GenericArrayType.
* To make this class easier to understand and maintain, there is a separate isAssignableFrom method for each combination
* of possible types. Each of these methods compares two type instances and determines whether the first one is assignable from
* the other.
*
* TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is performed.
*
* @author Jozef Hartinger
*
*/
public class CovariantTypes {
private CovariantTypes() {
}
public static boolean isAssignableFromAtLeastOne(Type type1, Type[] types2) {
for (Type type2 : types2) {
if (isAssignableFrom(type1, type2)) {
return true;
}
}
return false;
}
public static boolean isAssignableFrom(Type type1, Type type2) {
if (type1 instanceof Class<?>) {
if (type2 instanceof Class<?>) {
return isAssignableFrom((Class<?>) type1, (Class<?>) type2);
}
if (type2 instanceof ParameterizedType) {
return isAssignableFrom((Class<?>) type1, (ParameterizedType) type2);
}
if (type2 instanceof TypeVariable<?>) {
return isAssignableFrom((Class<?>) type1, (TypeVariable<?>) type2);
}
if (type2 instanceof WildcardType) {
return isAssignableFrom((Class<?>) type1, (WildcardType) type2);
}
if (type2 instanceof GenericArrayType) {
return isAssignableFrom((Class<?>) type1, (GenericArrayType) type2);
}
throw ReflectionLogger.LOG.unknownType(type2);
}
if (type1 instanceof ParameterizedType) {
if (type2 instanceof Class<?>) {
return isAssignableFrom((ParameterizedType) type1, (Class<?>) type2);
}
if (type2 instanceof ParameterizedType) {
return isAssignableFrom((ParameterizedType) type1, (ParameterizedType) type2);
}
if (type2 instanceof TypeVariable<?>) {
return isAssignableFrom((ParameterizedType) type1, (TypeVariable<?>) type2);
}
if (type2 instanceof WildcardType) {
return isAssignableFrom((ParameterizedType) type1, (WildcardType) type2);
}
if (type2 instanceof GenericArrayType) {
return isAssignableFrom((ParameterizedType) type1, (GenericArrayType) type2);
}
throw ReflectionLogger.LOG.unknownType(type2);
}
if (type1 instanceof TypeVariable<?>) {
if (type2 instanceof Class<?>) {
return isAssignableFrom((TypeVariable<?>) type1, (Class<?>) type2);
}
if (type2 instanceof ParameterizedType) {
return isAssignableFrom((TypeVariable<?>) type1, (ParameterizedType) type2);
}
if (type2 instanceof TypeVariable<?>) {
return isAssignableFrom((TypeVariable<?>) type1, (TypeVariable<?>) type2);
}
if (type2 instanceof WildcardType) {
return isAssignableFrom((TypeVariable<?>) type1, (WildcardType) type2);
}
if (type2 instanceof GenericArrayType) {
return isAssignableFrom((TypeVariable<?>) type1, (GenericArrayType) type2);
}
throw ReflectionLogger.LOG.unknownType(type2);
}
if (type1 instanceof WildcardType) {
if (Types.isActualType(type2)) {
return isAssignableFrom((WildcardType) type1, type2);
}
if (type2 instanceof TypeVariable<?>) {
return isAssignableFrom((WildcardType) type1, (TypeVariable<?>) type2);
}
if (type2 instanceof WildcardType) {
return isAssignableFrom((WildcardType) type1, (WildcardType) type2);
}
throw ReflectionLogger.LOG.unknownType(type2);
}
if (type1 instanceof GenericArrayType) {
if (type2 instanceof Class<?>) {
return isAssignableFrom((GenericArrayType) type1, (Class<?>) type2);
}
if (type2 instanceof ParameterizedType) {
return isAssignableFrom((GenericArrayType) type1, (ParameterizedType) type2);
}
if (type2 instanceof TypeVariable<?>) {
return isAssignableFrom((GenericArrayType) type1, (TypeVariable<?>) type2);
}
if (type2 instanceof WildcardType) {
return isAssignableFrom((GenericArrayType) type1, (WildcardType) type2);
}
if (type2 instanceof GenericArrayType) {
return isAssignableFrom((GenericArrayType) type1, (GenericArrayType) type2);
}
throw ReflectionLogger.LOG.unknownType(type2);
}
throw ReflectionLogger.LOG.unknownType(type1);
}
/*
* Raw type
*/
private static boolean isAssignableFrom(Class<?> type1, Class<?> type2) {
return boxedClass(type1).isAssignableFrom(boxedClass(type2));
}
private static boolean isAssignableFrom(Class<?> type1, ParameterizedType type2) {
return type1.isAssignableFrom(Reflections.getRawType(type2));
}
private static boolean isAssignableFrom(Class<?> type1, TypeVariable<?> type2) {
for (Type type : type2.getBounds()) {
if (isAssignableFrom(type1, type)) {
return true;
}
}
return false;
}
private static boolean isAssignableFrom(Class<?> type1, WildcardType type2) {
return false;
}
private static boolean isAssignableFrom(Class<?> type1, GenericArrayType type2) {
return type1.equals(Object.class) || type1.isArray()
&& isAssignableFrom(type1.getComponentType(), Reflections.getRawType(type2.getGenericComponentType()));
}
/*
* ParameterizedType
*/
private static boolean isAssignableFrom(ParameterizedType type1, Class<?> type2) {
Class<?> rawType1 = Reflections.getRawType(type1);
// raw types have to be assignable
if (!isAssignableFrom(rawType1, type2)) {
return false;
}
// this is a raw type with missing type arguments
if (!Types.getCanonicalType(type2).equals(type2)) {
return true;
}
return matches(type1, new HierarchyDiscovery(type2));
}
private static boolean isAssignableFrom(ParameterizedType type1, ParameterizedType type2) {
// first, raw types have to be assignable
if (!isAssignableFrom(Reflections.getRawType(type1), Reflections.getRawType(type2))) {
return false;
}
if (matches(type1, type2)) {
return true;
}
return matches(type1, new HierarchyDiscovery(type2));
}
private static boolean matches(ParameterizedType type1, HierarchyDiscovery type2) {
for (Type type : type2.getTypeClosure()) {
if (type instanceof ParameterizedType && matches(type1, (ParameterizedType) type)) {
return true;
}
}
return false;
}
private static boolean matches(ParameterizedType type1, ParameterizedType type2) {
final Class<?> rawType1 = Reflections.getRawType(type1);
final Class<?> rawType2 = Reflections.getRawType(type2);
if (!rawType1.equals(rawType2)) {
return false;
}
final Type[] types1 = type1.getActualTypeArguments();
final Type[] types2 = type2.getActualTypeArguments();
if (types1.length != types2.length) {
throw ReflectionLogger.LOG.invalidTypeArgumentCombination(type1, type2);
}
for (int i = 0; i < type1.getActualTypeArguments().length; i++) {
// Generics are invariant
if (!InvariantTypes.isAssignableFrom(types1[i], types2[i])) {
return false;
}
}
return true;
}
private static boolean isAssignableFrom(ParameterizedType type1, TypeVariable<?> type2) {
for (Type type : type2.getBounds()) {
if (isAssignableFrom(type1, type)) {
return true;
}
}
return false;
}
private static boolean isAssignableFrom(ParameterizedType type1, WildcardType type2) {
return false;
}
private static boolean isAssignableFrom(ParameterizedType type1, GenericArrayType type2) {
return false;
}
/*
* Type variable
*/
private static boolean isAssignableFrom(TypeVariable<?> type1, Class<?> type2) {
return false;
}
private static boolean isAssignableFrom(TypeVariable<?> type1, ParameterizedType type2) {
return false;
}
/**
* Returns <tt>true</tt> if <tt>type2</tt> is a "sub-variable" of <tt>type1</tt>, i.e. if they are equal or if
* <tt>type2</tt> (transitively) extends <tt>type1</tt>.
*/
private static boolean isAssignableFrom(TypeVariable<?> type1, TypeVariable<?> type2) {
if (type1.equals(type2)) {
return true;
}
// if a type variable extends another type variable, it cannot declare other bounds
if (type2.getBounds()[0] instanceof TypeVariable<?>) {
return isAssignableFrom(type1, (TypeVariable<?>) type2.getBounds()[0]);
}
return false;
}
private static boolean isAssignableFrom(TypeVariable<?> type1, WildcardType type2) {
return false;
}
private static boolean isAssignableFrom(TypeVariable<?> type1, GenericArrayType type2) {
return false;
}
/*
* Wildcard
*/
/**
* This logic is shared for all actual types i.e. raw types, parameterized types and generic array types.
*/
private static boolean isAssignableFrom(WildcardType type1, Type type2) {
if (!isAssignableFrom(type1.getUpperBounds()[0], type2)) {
return false;
}
if (type1.getLowerBounds().length > 0 && !isAssignableFrom(type2, type1.getLowerBounds()[0])) {
return false;
}
return true;
}
private static boolean isAssignableFrom(WildcardType type1, TypeVariable<?> type2) {
if (type1.getLowerBounds().length > 0) {
return isAssignableFrom(type2, type1.getLowerBounds()[0]);
}
return isAssignableFrom(type1.getUpperBounds()[0], type2);
}
private static boolean isAssignableFrom(WildcardType type1, WildcardType type2) {
if (!isAssignableFrom(type1.getUpperBounds()[0], type2.getUpperBounds()[0])) {
return false;
}
if (type1.getLowerBounds().length > 0) {
// the first type defines a lower bound
if (type2.getLowerBounds().length > 0) {
return isAssignableFrom(type2.getLowerBounds()[0], type1.getLowerBounds()[0]);
} else {
return false;
}
} else if (type2.getLowerBounds().length > 0) {
// only the second type defines a lower bound
return type1.getUpperBounds()[0].equals(Object.class);
}
return true;
}
/*
* GenericArrayType
*/
private static boolean isAssignableFrom(GenericArrayType type1, Class<?> type2) {
return type2.isArray() && isAssignableFrom(Reflections.getRawType(type1.getGenericComponentType()), type2.getComponentType());
}
private static boolean isAssignableFrom(GenericArrayType type1, ParameterizedType type2) {
return false;
}
private static boolean isAssignableFrom(GenericArrayType type1, TypeVariable<?> type2) {
/*
* JLS does not allow array types to be used as bounds of type variables
*/
return false;
}
private static boolean isAssignableFrom(GenericArrayType type1, WildcardType type2) {
return false;
}
private static boolean isAssignableFrom(GenericArrayType type1, GenericArrayType type2) {
return isAssignableFrom(type1.getGenericComponentType(), type2.getGenericComponentType());
}
}