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

io.micronaut.kotlin.processing.visitor.AbstractKotlinElement.kt Maven / Gradle / Ivy

There is a newer version: 4.7.10
Show newest version
/*
 * Copyright 2017-2022 original authors
 *
 * 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
 *
 * https://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 io.micronaut.kotlin.processing.visitor

import com.google.devtools.ksp.*
import com.google.devtools.ksp.symbol.*
import io.micronaut.core.annotation.AnnotationMetadata
import io.micronaut.inject.ast.ClassElement
import io.micronaut.inject.ast.Element
import io.micronaut.inject.ast.ElementModifier
import io.micronaut.inject.ast.PrimitiveElement
import io.micronaut.inject.ast.WildcardElement
import io.micronaut.inject.ast.annotation.AbstractAnnotationElement
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory
import io.micronaut.kotlin.processing.getBinaryName
import io.micronaut.kotlin.processing.getClassDeclaration
import java.util.*

internal abstract class AbstractKotlinElement(
    private val nativeType: T,
    annotationMetadataFactory: ElementAnnotationMetadataFactory,
    protected val visitorContext: KotlinVisitorContext
) : AbstractAnnotationElement(annotationMetadataFactory) {

    private val annotatedInfo = nativeType.element

    override fun getNativeType(): T = nativeType

    override fun isProtected() = if (annotatedInfo is KSDeclaration) {
        annotatedInfo.getVisibility() == Visibility.PROTECTED
    } else {
        false
    }

    override fun isStatic() = if (annotatedInfo is KSDeclaration) {
        annotatedInfo.modifiers.contains(Modifier.JAVA_STATIC)
    } else {
        false
    }

    private fun makeCopy(): AbstractKotlinElement {
        val element: AbstractKotlinElement = copyThis()
        copyValues(element)
        return element
    }

    /**
     * @return copy of this element
     */
    protected abstract fun copyThis(): AbstractKotlinElement

    /**
     * @param element the values to be copied to
     */
    protected open fun copyValues(element: AbstractKotlinElement) {
        element.presetAnnotationMetadata = presetAnnotationMetadata
    }

    override fun withAnnotationMetadata(annotationMetadata: AnnotationMetadata): Element? {
        val kotlinElement: AbstractKotlinElement = makeCopy()
        kotlinElement.presetAnnotationMetadata = annotationMetadata
        return kotlinElement
    }

    override fun isPublic() = if (annotatedInfo is KSDeclaration) {
        annotatedInfo.getVisibility() == Visibility.PUBLIC
    } else {
        false
    }

    override fun isPrivate() = if (annotatedInfo is KSDeclaration) {
        annotatedInfo.getVisibility() == Visibility.PRIVATE
    } else {
        false
    }

    override fun isPackagePrivate() = if (annotatedInfo is KSDeclaration) {
        annotatedInfo.isJavaPackagePrivate()
    } else {
        false
    }

    override fun isFinal() = if (annotatedInfo is KSDeclaration) {
        !annotatedInfo.isOpen() || annotatedInfo.modifiers.contains(Modifier.FINAL)
    } else {
        false
    }

    override fun isAbstract(): Boolean {
        return if (annotatedInfo is KSModifierListOwner) {
            annotatedInfo.modifiers.contains(Modifier.ABSTRACT)
        } else {
            false
        }
    }

    @OptIn(KspExperimental::class)
    override fun getModifiers(): MutableSet {
        if (annotatedInfo is KSDeclaration) {
            val javaModifiers = visitorContext.resolver.effectiveJavaModifiers(annotatedInfo)
            return javaModifiers.mapNotNull {
                when (it) {
                    Modifier.ABSTRACT -> ElementModifier.ABSTRACT
                    Modifier.FINAL -> ElementModifier.FINAL
                    Modifier.PRIVATE -> ElementModifier.PRIVATE
                    Modifier.PROTECTED -> ElementModifier.PROTECTED
                    Modifier.PUBLIC, Modifier.INTERNAL -> ElementModifier.PUBLIC
                    Modifier.JAVA_STATIC -> ElementModifier.STATIC
                    Modifier.JAVA_TRANSIENT -> ElementModifier.TRANSIENT
                    Modifier.JAVA_DEFAULT -> ElementModifier.DEFAULT
                    Modifier.JAVA_SYNCHRONIZED -> ElementModifier.SYNCHRONIZED
                    Modifier.JAVA_VOLATILE -> ElementModifier.VOLATILE
                    Modifier.JAVA_NATIVE -> ElementModifier.NATIVE
                    Modifier.JAVA_STRICT -> ElementModifier.STRICTFP
                    else -> null
                }
            }.toMutableSet()
        }
        return super.getModifiers()
    }

    override fun getDocumentation(): Optional {
        return if (annotatedInfo is KSDeclaration) {
            Optional.ofNullable(annotatedInfo.docString)
        } else {
            Optional.empty()
        }
    }

    protected fun resolveDeclaringType(
        declaration: KSDeclaration,
        owningType: ClassElement
    ): ClassElement {
        var parent = declaration.parent
        if (parent is KSPropertyDeclaration) {
            parent = parent.parent
        }
        if (parent is KSFunctionDeclaration) {
            parent = parent.parent
        }
        return if (parent is KSClassDeclaration) {
            val className = parent.getBinaryName(visitorContext.resolver, visitorContext)
            if (owningType.name.equals(className)) {
                owningType
            } else {
                val parentTypeArguments = owningType.getTypeArguments(className)
                newKotlinClassElement(parent, parentTypeArguments)
            }
        } else {
            owningType
        }
    }

    protected fun resolveTypeArguments(
        owner: KotlinNativeElement,
        type: KSDeclaration,
        parentTypeArguments: Map,
        visitedTypes: MutableSet = HashSet()
    ): Map {
        val typeArguments = mutableMapOf()
        val typeParameters = type.typeParameters
        typeParameters.forEachIndexed { i, typeParameter ->
            typeArguments[typeParameters[i].name.asString()] =
                resolveTypeParameter(owner, typeParameter, parentTypeArguments, visitedTypes)
        }
        return typeArguments
    }

    protected fun resolveTypeParameter(
        owner: KotlinNativeElement,
        typeParameter: KSTypeParameter,
        parentTypeArguments: Map,
        visitedTypes: MutableSet = HashSet()
    ): ClassElement {
        val variableName = typeParameter.name.asString()
        val found = parentTypeArguments[variableName]
        if (found is PrimitiveElement) {
            return found
        }
        var bound = found as KotlinClassElement?
        if (bound is WildcardElement && !bound.isBounded) {
            bound = null
        }
        val parent = typeParameter.parent
        val thisNode = annotatedInfo
        val declaringElement = if (thisNode == parent) {
            this
        } else if (parent is KSClassDeclaration) {
            newKotlinClassElement(parent, emptyMap(), visitedTypes, true)
        } else {
            null
        }
        val stripTypeArguments = !visitedTypes.add(typeParameter)
        val bounds = typeParameter.bounds.map {
            val argumentType = it.resolve()
            newKotlinClassElement(
                owner,
                argumentType,
                parentTypeArguments,
                visitedTypes,
                stripTypeArguments
            )
        }.ifEmpty {
            mutableListOf(getJavaObjectClassElement()).asSequence()
        }.toList()

        return KotlinGenericPlaceholderElement(
            KotlinTypeParameterNativeElement(typeParameter, owner),
            bound,
            bounds,
            declaringElement,
            elementAnnotationMetadataFactory,
            visitorContext
        )
    }

    private fun getJavaObjectClassElement() =
        visitorContext.getClassElement(Object::class.java.name).get() as KotlinClassElement

    protected fun resolveTypeArguments(
        owner: KotlinNativeElement,
        type: KSType,
        parentTypeArguments: Map,
        visitedTypes: MutableSet = HashSet()
    ): Map {
        val typeArguments = mutableMapOf()
        val typeParameters = type.declaration.typeParameters
        if (type.arguments.isEmpty()) {
            typeParameters.forEach {
                typeArguments[it.name.asString()] =
                    resolveTypeParameter(owner, it, parentTypeArguments, visitedTypes)
            }
        } else {
            type.arguments.forEachIndexed { i, typeArgument ->
                val variableName = typeParameters[i].name.asString()
                typeArguments[variableName] =
                    resolveTypeArgument(owner, typeArgument, parentTypeArguments, visitedTypes)
            }
        }
        return typeArguments
    }

    private fun resolveEmptyTypeArguments(declaration: KSClassDeclaration): Map {
        val objectElement = getJavaObjectClassElement()
        val typeArguments = mutableMapOf()
        val typeParameters = declaration.typeParameters
        typeParameters.forEach {
            typeArguments[it.name.asString()] = objectElement
        }
        return typeArguments
    }

    private fun resolveTypeArgument(
        owner: KotlinNativeElement,
        typeArgument: KSTypeArgument,
        parentTypeArguments: Map,
        visitedTypes: MutableSet
    ): ClassElement {

        return when (typeArgument.variance) {
            Variance.STAR, Variance.COVARIANT, Variance.CONTRAVARIANT -> {
                // example List<*>, IN, OUT
                val type = typeArgument.type!!
                val stripTypeArguments = !visitedTypes.add(type)
                val upperBounds =
                    resolveUpperBounds(
                        owner,
                        typeArgument,
                        parentTypeArguments,
                        visitedTypes,
                        stripTypeArguments
                    )
                val lowerBounds = resolveLowerBounds(
                    owner,
                    typeArgument,
                    parentTypeArguments,
                    visitedTypes,
                    stripTypeArguments
                )
                val upper = WildcardElement.findUpperType(upperBounds, lowerBounds)!!
                KotlinWildcardElement(
                    KotlinTypeArgumentNativeElement(typeArgument, owner),
                    upper,
                    upperBounds,
                    lowerBounds,
                    elementAnnotationMetadataFactory,
                    visitorContext,
                    typeArgument.variance == Variance.STAR
                )
            }

            // List
            else -> {
                resolveTypeArgumentType(owner, typeArgument, parentTypeArguments, visitedTypes)
            }
        }
    }

    private fun resolveLowerBounds(
        owner: KotlinNativeElement,
        typeArgument: KSTypeArgument,
        parentTypeArguments: Map,
        visitedTypes: MutableSet,
        stripTypeArguments: Boolean,
    ): List {
        return if (typeArgument.variance == Variance.CONTRAVARIANT) {
            listOf(
                resolveTypeArgumentType(
                    owner,
                    typeArgument,
                    parentTypeArguments,
                    visitedTypes,
                    stripTypeArguments
                ) as KotlinClassElement
            )
        } else {
            return emptyList()
        }
    }

    private fun resolveUpperBounds(
        owner: KotlinNativeElement,
        typeArgument: KSTypeArgument,
        parentTypeArguments: Map = emptyMap(),
        visitedTypes: MutableSet,
        stripTypeArguments: Boolean
    ): List {
        return when (typeArgument.variance) {
            Variance.COVARIANT, Variance.STAR -> {
                listOf(
                    resolveTypeArgumentType(
                        owner,
                        typeArgument,
                        parentTypeArguments,
                        visitedTypes,
                        stripTypeArguments
                    ) as KotlinClassElement
                )
            }

            else -> {
                val objectType =
                    visitorContext.resolver.getClassDeclarationByName(Object::class.java.name)!!
                listOf(
                    newKotlinClassElement(objectType, parentTypeArguments, visitedTypes)
                )
            }
        }
    }

    protected fun newKotlinClassElement(
        declaration: KSClassDeclaration,
        parentTypeArguments: Map = emptyMap(),
        visitedTypes: MutableSet = HashSet(),
        stripTypeArguments: Boolean = false,
    ) = newClassElement(
        null,
        null,
        declaration,
        parentTypeArguments,
        visitedTypes,
        false,
        stripTypeArguments
    ) as KotlinClassElement

    protected fun newClassElement(
        declaration: KSClassDeclaration,
        parentTypeArguments: Map = emptyMap(),
        visitedTypes: MutableSet = HashSet(),
        stripTypeArguments: Boolean = false,
    ) = newClassElement(
        null,
        null,
        declaration,
        parentTypeArguments,
        visitedTypes,
        true,
        stripTypeArguments
    )

    private fun newKotlinClassElement(
        owner: KotlinNativeElement?,
        type: KSType,
        parentTypeArguments: Map = emptyMap(),
        visitedTypes: MutableSet = HashSet(),
        stripTypeArguments: Boolean = false,
    ) = newClassElement(
        owner,
        type,
        type.declaration.getClassDeclaration(visitorContext),
        parentTypeArguments,
        visitedTypes,
        false,
        stripTypeArguments
    ) as KotlinClassElement

    private fun resolveTypeArgumentType(
        owner: KotlinNativeElement,
        typeArgument: KSTypeArgument,
        parentTypeArguments: Map,
        visitedTypes: MutableSet = HashSet(),
        stripTypeArguments: Boolean = false
    ): ClassElement {
        val type = typeArgument.type
        val resolvedType = type!!.resolve()
        val stripTypeArguments2 = stripTypeArguments || !visitedTypes.add(type)

        val resolved = newTypeArgument(
            owner,
            resolvedType,
            parentTypeArguments,
            visitedTypes,
            stripTypeArguments2
        )
        if (resolved !is KotlinClassElement || resolved.isGenericPlaceholder) {
            return resolved
        }

        return KotlinTypeArgumentElement(
            KotlinTypeArgumentNativeElement(typeArgument, owner),
            resolved,
            visitorContext
        )
    }

    protected fun newClassElement(
        owner: KotlinNativeElement?,
        type: KSType,
        parentTypeArguments: Map = emptyMap()
    ) = newClassElement(
        owner,
        type,
        type.declaration.getClassDeclaration(visitorContext),
        parentTypeArguments,
        HashSet()
    )

    private fun newTypeArgument(
        owner: KotlinNativeElement?,
        type: KSType,
        parentTypeArguments: Map,
        visitedTypes: MutableSet = HashSet(),
        stripTypeArguments: Boolean = false,
    ) = newClassElement(
        owner,
        type,
        type.declaration.getClassDeclaration(visitorContext),
        parentTypeArguments,
        visitedTypes,
        false,
        stripTypeArguments
    )

    private fun newClassElement(
        owner: KotlinNativeElement?,
        type: KSType?,
        declaration: KSClassDeclaration,
        parentTypeArguments: Map,
        visitedTypes: MutableSet,
        allowPrimitive: Boolean = true,
        stripTypeArguments: Boolean = false
    ): ClassElement {
        if (type != null) {
            val typeDeclaration = type.declaration
            if (typeDeclaration is KSTypeParameter) {
                return resolveTypeParameter(
                    owner!!,
                    typeDeclaration,
                    parentTypeArguments,
                    visitedTypes
                )
            }
        }
        val qualifiedName = declaration.qualifiedName!!.asString()
        val primitiveArray = primitiveArrays[qualifiedName]
        if (primitiveArray != null) {
            return primitiveArray
        }
        val canBePrimitive =
            type == null || type.annotations.toList().isEmpty() && !type.isMarkedNullable
        if (allowPrimitive && canBePrimitive) {
            val element = primitives[qualifiedName]
            if (element != null) {
                return element
            }
        }
        if (type != null && qualifiedName == "kotlin.Array") {
            val component = type.arguments[0].type!!.resolve()
            return newTypeArgument(
                owner,
                component,
                parentTypeArguments,
                visitedTypes,
                false
            ).toArray()
        }
        val typeArguments = if (stripTypeArguments) {
            resolveEmptyTypeArguments(declaration)
        } else if (type == null) {
            resolveTypeArguments(
                nativeType,
                declaration,
                parentTypeArguments,
                visitedTypes
            )
        } else {
            resolveTypeArguments(
                nativeType,
                type,
                parentTypeArguments,
                visitedTypes
            )
        }
        return if (declaration.classKind == ClassKind.ENUM_CLASS) {
            KotlinEnumElement(
                KotlinClassNativeElement(declaration, type, owner),
                elementAnnotationMetadataFactory,
                visitorContext,
                typeArguments
            )
        } else {
            KotlinClassElement(
                KotlinClassNativeElement(declaration, type, owner),
                elementAnnotationMetadataFactory,
                typeArguments,
                visitorContext
            )
        }
    }

    override fun toString(): String {
        return getDescription(false)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is AbstractKotlinElement<*>) return false
        if (isAnyOrObject() && other.isAnyOrObject()) return true
        if (nativeType.element != other.nativeType.element) return false
        return true
    }

    private fun isAnyOrObject(): Boolean {
        return name.equals(Object::class.java.name) || name.equals(Any::class.java.name)
    }

    override fun hashCode(): Int {
        return nativeType.element.hashCode()
    }

    companion object {
        val primitives = mapOf(
            "kotlin.Boolean" to PrimitiveElement.BOOLEAN,
            "kotlin.Char" to PrimitiveElement.CHAR,
            "kotlin.Short" to PrimitiveElement.SHORT,
            "kotlin.Int" to PrimitiveElement.INT,
            "kotlin.Long" to PrimitiveElement.LONG,
            "kotlin.Float" to PrimitiveElement.FLOAT,
            "kotlin.Double" to PrimitiveElement.DOUBLE,
            "kotlin.Byte" to PrimitiveElement.BYTE,
            "kotlin.Unit" to PrimitiveElement.VOID
        )
        val primitiveArrays = mapOf(
            "kotlin.BooleanArray" to PrimitiveElement.BOOLEAN.toArray(),
            "kotlin.CharArray" to PrimitiveElement.CHAR.toArray(),
            "kotlin.ShortArray" to PrimitiveElement.SHORT.toArray(),
            "kotlin.IntArray" to PrimitiveElement.INT.toArray(),
            "kotlin.LongArray" to PrimitiveElement.LONG.toArray(),
            "kotlin.FloatArray" to PrimitiveElement.FLOAT.toArray(),
            "kotlin.DoubleArray" to PrimitiveElement.DOUBLE.toArray(),
            "kotlin.ByteArray" to PrimitiveElement.BYTE.toArray(),
        )
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy