io.micronaut.kotlin.processing.visitor.AbstractKotlinElement.kt Maven / Gradle / Ivy
/*
* 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