AssignBack.java

package space.sunqian.common.reflect;

import space.sunqian.annotations.Nonnull;

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 java.util.List;
import java.util.Objects;

final class AssignBack {

    public static boolean isAssignable(@Nonnull Type assigned, @Nonnull Type assignee) {
        if (assigned instanceof Class<?>) {
            if (Objects.equals(assigned, assignee) || Objects.equals(assigned, Object.class)) {
                return true;
            }
            if (assignee instanceof Class<?>) {
                return ((Class<?>) assigned).isAssignableFrom((Class<?>) assignee);
            }
            if (assignee instanceof ParameterizedType) {
                return isAssignable((Class<?>) assigned, (ParameterizedType) assignee);
            }
            if (assignee instanceof WildcardType) {
                return isAssignable(assigned, (WildcardType) assignee);
            }
            if (assignee instanceof TypeVariable<?>) {
                return isAssignable(assigned, (TypeVariable<?>) assignee);
            }
            if (assignee instanceof GenericArrayType) {
                return isAssignable((Class<?>) assigned, (GenericArrayType) assignee);
            }
        } else if (assigned instanceof ParameterizedType) {
            if (Objects.equals(assigned, assignee)) {
                return true;
            }
            if (assignee instanceof Class<?>) {
                return isAssignable((ParameterizedType) assigned, (Class<?>) assignee);
            }
            if (assignee instanceof ParameterizedType) {
                return isAssignable((ParameterizedType) assigned, (ParameterizedType) assignee);
            }
            if (assignee instanceof WildcardType) {
                return isAssignable(assigned, (WildcardType) assignee);
            }
            if (assignee instanceof TypeVariable<?>) {
                return isAssignable(assigned, (TypeVariable<?>) assignee);
            }
            // if (assignee instanceof GenericArrayType) {
            //     return false;
            // }
        } else if (assigned instanceof WildcardType) {
            return isAssignable((WildcardType) assigned, assignee);
        } else if (assigned instanceof TypeVariable<?>) {
            if (Objects.equals(assigned, assignee)) {
                return true;
            }
            // if (assignee instanceof Class<?>) {
            //     return isAssignable((TypeVariable<?>) assigned, (Class<?>) assignee);
            // }
            // if (assignee instanceof ParameterizedType) {
            //     return isAssignable((TypeVariable<?>) assigned, (ParameterizedType) assignee);
            // }
            if (assignee instanceof WildcardType) {
                return isAssignable(assigned, (WildcardType) assignee);
            }
            if (assignee instanceof TypeVariable<?>) {
                return isAssignable(assigned, (TypeVariable<?>) assignee);
            }
            // if (assignee instanceof GenericArrayType) {
            //     return isAssignable((TypeVariable<?>) assigned, (GenericArrayType) assignee);
            // }
        } else if (assigned instanceof GenericArrayType) {
            if (Objects.equals(assigned, assignee)) {
                return true;
            }
            if (assignee instanceof Class<?>) {
                return isAssignable((GenericArrayType) assigned, (Class<?>) assignee);
            }
            if (assignee instanceof ParameterizedType) {
                return isAssignable((GenericArrayType) assigned, (ParameterizedType) assignee);
            }
            if (assignee instanceof WildcardType) {
                return isAssignable(assigned, (WildcardType) assignee);
            }
            if (assignee instanceof TypeVariable<?>) {
                return isAssignable(assigned, (TypeVariable<?>) assignee);
            }
            if (assignee instanceof GenericArrayType) {
                return isAssignable((GenericArrayType) assigned, (GenericArrayType) assignee);
            }
        }
        return false;
    }

    private static boolean isAssignable(@Nonnull Class<?> assigned, @Nonnull ParameterizedType assignee) {
        Type assigneeRaw = assignee.getRawType();
        if (!(assigneeRaw instanceof Class<?>)) {
            return false;
        }
        return assigned.isAssignableFrom((Class<?>) assigneeRaw);
    }

    private static boolean isAssignable(@Nonnull Class<?> assigned, @Nonnull GenericArrayType assignee) {
        if (!assigned.isArray()) {
            return false;
        }
        Type assignedComponent = assigned.getComponentType();
        Type assigneeComponent = assignee.getGenericComponentType();
        return isAssignable(assignedComponent, assigneeComponent);
    }

    private static boolean isAssignable(@Nonnull ParameterizedType assigned, @Nonnull Class<?> assignee) {
        Class<?> assignedRaw = (Class<?>) assigned.getRawType();
        return assignedRaw.isAssignableFrom(assignee);
    }

    private static boolean isAssignable(@Nonnull ParameterizedType assigned, @Nonnull ParameterizedType assignee) {
        Type assignedRaw = assigned.getRawType();
        if (!(assignedRaw instanceof Class<?>)) {
            return false;
        }
        Type assigneeRaw = assignee.getRawType();
        if (!(assigneeRaw instanceof Class<?>)) {
            return false;
        }
        if (!((Class<?>) assignedRaw).isAssignableFrom((Class<?>) assigneeRaw)) {
            return false;
        }
        List<Type> assigneeArgs = TypeKit.resolveActualTypeArguments(assignee, (Class<?>) assignedRaw);
        Type[] assignedArgs = assigned.getActualTypeArguments();
        if (assignedArgs.length != assigneeArgs.size()) {
            return false;
        }
        for (int i = 0; i < assignedArgs.length; i++) {
            Type assignedArg = assignedArgs[i];
            Type assigneeArg = assigneeArgs.get(i);
            if (assignedArg instanceof WildcardType) {
                if (!isAssignableParameterizedArgs((WildcardType) assignedArg, assigneeArg)) {
                    return false;
                }
                continue;
            }
            if (!Objects.equals(assignedArg, assigneeArg)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isAssignableParameterizedArgs(@Nonnull WildcardType assignedArg, @Nonnull Type assigneeArg) {
        Type assignedUpper = TypeKit.getUpperBound(assignedArg);
        if (!isAssignable(assignedUpper, assigneeArg)) {
            return false;
        }
        Type assignedLower = TypeKit.getLowerBound(assignedArg);
        return assignedLower == null || isAssignable(assigneeArg, assignedLower);
    }

    private static boolean isAssignable(@Nonnull WildcardType assigned, @Nonnull Type assignee) {
        Type lowerType = TypeKit.getLowerBound(assigned);
        if (lowerType != null) {
            return isAssignable(lowerType, assignee);
        }
        return false;
    }

    private static boolean isAssignable(@Nonnull Type assigned, @Nonnull WildcardType assignee) {
        Type assigneeUpper = TypeKit.getUpperBound(assignee);
        return isAssignable(assigned, assigneeUpper);
    }

    private static boolean isAssignable(@Nonnull Type assigned, @Nonnull TypeVariable<?> assignee) {
        Type[] assigneeUppers = assignee.getBounds();
        for (Type assigneeUpper : assigneeUppers) {
            if (isAssignable(assigned, assigneeUpper)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isAssignable(@Nonnull GenericArrayType assigned, @Nonnull Class<?> assignee) {
        if (!assignee.isArray()) {
            return false;
        }
        Type assignedComponent = assigned.getGenericComponentType();
        Type assigneeComponent = assignee.getComponentType();
        return isAssignable(assignedComponent, assigneeComponent);
    }

    private static boolean isAssignable(@Nonnull GenericArrayType assigned, @Nonnull ParameterizedType assignee) {
        return false;
    }

    private static boolean isAssignable(@Nonnull GenericArrayType assigned, @Nonnull GenericArrayType assignee) {
        Type assignedComponent = assigned.getGenericComponentType();
        Type assigneeComponent = assignee.getGenericComponentType();
        return isAssignable(assignedComponent, assigneeComponent);
    }

    private AssignBack() {
    }
}