class Types {

	public static boolean isAssignableFrom(Type t1, Type t2) {
		checkNotNull(t1);
		checkNotNull(t2);

		if (t1.equals(t2)) {
			// easy case, types are equals
			return true;
		}

		if (t1 instanceof Class<?>) {
			/*
				first type is a reifiable class, so any raw type, generic or not,
				which is assignable by the raw type of t1 means that t1 is assignable from t2
				and the reverse is also true
			 */
			return ((Class<?>) t1).isAssignableFrom(getRawType(t2));
		}

		if (t1 instanceof ParameterizedType) {
			Class<?> rawTypeT1 = getRawType(t1);
			Class<?> rawTypeT2 = getRawType(t2);

			if (!rawTypeT1.isAssignableFrom(rawTypeT2) || rawTypeT1.equals(rawTypeT2)) {
				/*
					don't need to go further and to check generics, raw type of first type is not assignable
					from second one, or raw types are equals but type aren't, so has one can not reference
					a same type having a different generic type than his, it also means that t1 is not
					assignable from t2
				 */
				return false;
			}

			if (t2 instanceof Class<?>) {
				/*
					we need to launch a recursive analysis on generic super type, and generic interfaces, because
					t2 is a reifiable class, so there is no way that t1 can be directly assigned from t2,
					so it has to be one of the sub-types of t2
				 */

				Type genericSuperclass = ((Class) t2).getGenericSuperclass();
				if (genericSuperclass != null) {
					if (isAssignableFrom(t1, genericSuperclass)) {
						return true;
					}
				}
				Type[] genericInterfaces = ((Class) t2).getGenericInterfaces();
				for (Type genericInterface : genericInterfaces) {
					if (isAssignableFrom(t1, genericInterface)) {
						return true;
					}
				}

				return false;
			}

			if (t2 instanceof ParameterizedType) {
				/*
					Extract generics type, and variables of the class, in order to know the concrete type of each variables
				 */
				Type[] actualTypeArguments = ((ParameterizedType) t2).getActualTypeArguments();
				TypeVariable<? extends Class<?>>[] typeParameters = rawTypeT2.getTypeParameters();
				Map<TypeVariable<? extends Class<?>>, Type> typesMap = Maps.newHashMapWithExpectedSize(typeParameters.length);
				for (int i = 0; i < typeParameters.length; i++) {
					typesMap.put(typeParameters[i], actualTypeArguments[i]);
				}

				/*
					Try with the superclass, and fill variable with concrete types
				 */
				Type genericSuperclass = rawTypeT2.getGenericSuperclass();
				if (genericSuperclass != null) {
					if (genericSuperclass instanceof Class<?>) {
						if (isAssignableFrom(t1, genericSuperclass)) {
							return true;
						}
					} else if (genericSuperclass instanceof ParameterizedType) {
						/*
							We need to replace type parameters with concrete types
						 */
						ParameterizedType genericSuperclassParameterized = (ParameterizedType) genericSuperclass;
						Type[] genericSuperclassActualTypeArguments = genericSuperclassParameterized.getActualTypeArguments();
						for (int i = 0; i < genericSuperclassActualTypeArguments.length; i++) {
							if (genericSuperclassActualTypeArguments[i] instanceof TypeVariable) {
								genericSuperclassActualTypeArguments[i] = typesMap.get(genericSuperclassActualTypeArguments[i]);
							}
						}
						Type genericSuperclassRawType = genericSuperclassParameterized.getRawType();
						if (isAssignableFrom(t1, Types.newParameterizedType((Class<?>) genericSuperclassRawType,
								genericSuperclassActualTypeArguments))) {
							return true;
						}
					}
				}

				/*
					Try the same thing with interfaces
				 */
				Type[] genericInterfaces = rawTypeT2.getGenericInterfaces();
				for (Type genericInterface : genericInterfaces) {
					if (genericInterface instanceof Class<?>) {
						if (isAssignableFrom(t1, genericInterface)) {
							return true;
						}
					} else if (genericInterface instanceof ParameterizedType) {
						/*
							We need to replace type parameters with concrete types
						 */
						ParameterizedType genericInterfaceParameterized = (ParameterizedType) genericInterface;
						Type[] genericInterfaceActualTypeArguments = genericInterfaceParameterized.getActualTypeArguments();
						for (int i = 0; i < genericInterfaceActualTypeArguments.length; i++) {
							if (genericInterfaceActualTypeArguments[i] instanceof TypeVariable) {
								genericInterfaceActualTypeArguments[i] = typesMap.get(genericInterfaceActualTypeArguments[i]);
							}
						}
						Type genericInterfaceRawType = genericInterfaceParameterized.getRawType();
						if (isAssignableFrom(t1, Types.newParameterizedType((Class<?>) genericInterfaceRawType,
								genericInterfaceActualTypeArguments))) {
							return true;
						}
					}
				}

				return false;

			}
		}
		return false;
	}

}