All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.kickstart.tools.GenericType.kt Maven / Gradle / Ivy

package graphql.kickstart.tools

import com.fasterxml.classmate.ResolvedType
import graphql.kickstart.tools.util.JavaType
import graphql.kickstart.tools.util.ParameterizedTypeImpl
import graphql.kickstart.tools.util.Primitives
import graphql.kickstart.tools.util.unwrap
import org.apache.commons.lang3.reflect.TypeUtils
import java.lang.reflect.ParameterizedType
import java.lang.reflect.TypeVariable
import java.lang.reflect.WildcardType

/**
 * @author Andrew Potter
 */
internal open class GenericType(protected val mostSpecificType: JavaType, protected val options: SchemaParserOptions) {

    fun isTypeAssignableFromRawClass(type: ParameterizedType, clazz: Class<*>) =
        clazz.isAssignableFrom(getRawClass(type.rawType))

    fun getRawClass() = getRawClass(mostSpecificType)

    fun getRawClass(type: JavaType): Class<*> = TypeUtils.getRawType(type, mostSpecificType)

    fun isAssignableFrom(type: JavaType) = TypeUtils.isAssignable(type, mostSpecificType)

    fun relativeToPotentialParent(declaringType: JavaType): RelativeTo {
        if (declaringType !is Class<*> || declaringType.isInterface) {
            return relativeToType(declaringType)
        }

        val type = getGenericSuperType(mostSpecificType, declaringType)
        if (type == null) {
            error("Unable to find generic type of class ${TypeUtils.toString(declaringType)} relative to ${TypeUtils.toString(mostSpecificType)}")
        } else {
            return relativeToType(type)
        }
    }

    fun relativeToType(declaringType: JavaType) = RelativeTo(declaringType, mostSpecificType, options)

    fun getGenericInterface(targetInterface: Class<*>) = getGenericInterface(mostSpecificType, targetInterface)

    private fun getGenericInterface(type: JavaType?, targetInterface: Class<*>): JavaType? {
        if (type == null) {
            return null
        }

        val raw = type as? Class<*> ?: getRawClass(type)

        if (raw == targetInterface) {
            return type
        }

        val possibleSubInterface = raw.genericInterfaces.find { genericInterface ->
            TypeUtils.isAssignable(genericInterface, targetInterface)
        } ?: raw.interfaces.find { iface ->
            TypeUtils.isAssignable(iface, targetInterface)
        } ?: getGenericInterface(raw.genericSuperclass, targetInterface) ?: return null

        return getGenericInterface(possibleSubInterface, targetInterface)
    }

    fun getGenericSuperType(targetSuperClass: Class<*>) = getGenericSuperType(mostSpecificType, targetSuperClass)

    private fun getGenericSuperType(type: JavaType?, targetSuperClass: Class<*>): JavaType? {
        if (type == null) {
            return null
        }

        val raw = type as? Class<*> ?: TypeUtils.getRawType(type, type)

        if (raw == targetSuperClass) {
            return type
        }

        return getGenericSuperType(raw.genericSuperclass, targetSuperClass)
    }

    class RelativeTo(private val declaringType: JavaType, mostSpecificType: JavaType, options: SchemaParserOptions) : GenericType(mostSpecificType, options) {

        /**
         * Unwrap certain Java types to find the "real" class.
         */
        fun unwrapGenericType(javaType: JavaType): JavaType {
            val type = replaceTypeVariable(javaType)
            return when (type) {
                is ParameterizedType -> {
                    val rawType = type.rawType
                    val genericType = options.genericWrappers.find { it.type == rawType }
                        ?: return type

                    val typeArguments = type.actualTypeArguments
                    if (typeArguments.size <= genericType.index) {
                        throw IndexOutOfBoundsException("Generic type '${TypeUtils.toString(type)}' does not have a type argument at index ${genericType.index}!")
                    }

                    val unwrapsTo = genericType.schemaWrapper.invoke(typeArguments[genericType.index])
                    return unwrapGenericType(unwrapsTo)
                }
                is TypeVariable<*> -> {
                    val parameterizedDeclaringType = parameterizedDeclaringTypeOrSuperType(declaringType)
                    if (parameterizedDeclaringType != null) {
                        unwrapGenericType(parameterizedDeclaringType, type)
                    } else {
                        error("Could not resolve type variable '${TypeUtils.toLongString(type)}' because declaring type is not parameterized: ${TypeUtils.toString(declaringType)}")
                    }
                }
                is WildcardType -> type.upperBounds.firstOrNull()
                    ?: throw error("Unable to unwrap type, wildcard has no upper bound: $type")
                is Class<*> -> if (type.isPrimitive) Primitives.wrap(type) else type
                else -> error("Unable to unwrap type: $type")
            }
        }

        private fun parameterizedDeclaringTypeOrSuperType(declaringType: JavaType): ParameterizedType? =
            if (declaringType is ParameterizedType) {
                declaringType
            } else {
                val superclass = declaringType.unwrap().genericSuperclass
                if (superclass != null) {
                    parameterizedDeclaringTypeOrSuperType(superclass)
                } else {
                    null
                }
            }

        private fun unwrapGenericType(declaringType: ParameterizedType, type: TypeVariable<*>): JavaType {
            val rawClass = getRawClass(mostSpecificType)
            val arguments = TypeUtils.determineTypeArguments(rawClass, declaringType)
            val matchingType = arguments
                .filter { it.key.name == type.name }
                .values
                .firstOrNull()
                ?: error("No type variable found for: ${TypeUtils.toLongString(type)}")

            return unwrapGenericType(matchingType)
        }

        private fun replaceTypeVariable(type: JavaType): JavaType {
            return when (type) {
                is ParameterizedType -> {
                    val actualTypeArguments = type.actualTypeArguments.map { replaceTypeVariable(it) }.toTypedArray()
                    ParameterizedTypeImpl.make(type.rawType as Class<*>?, actualTypeArguments, type.ownerType)
                }
                is ResolvedType -> {
                    if (type.typeParameters.isEmpty()) {
                        type.erasedType
                    } else {
                        val actualTypeArguments = type.typeParameters.map { replaceTypeVariable(it) }.toTypedArray()
                        ParameterizedTypeImpl.make(type.erasedType, actualTypeArguments, null)
                    }
                }
                is TypeVariable<*> -> {
                    if (declaringType is ParameterizedType) {
                        TypeUtils.getRawType(type, declaringType)
                    } else {
                        type
                    }
                }
                else -> {
                    type
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy