io.micronaut.kotlin.processing.visitor.KotlinClassElement.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.context.annotation.BeanProperties
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationReader
import io.micronaut.core.annotation.AnnotationMetadata
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.NonNull
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy
import io.micronaut.inject.ast.*
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory
import io.micronaut.inject.ast.annotation.MutableAnnotationMetadataDelegate
import io.micronaut.inject.ast.utils.AstBeanPropertiesUtils
import io.micronaut.inject.ast.utils.EnclosedElementsQuery
import io.micronaut.inject.processing.ProcessingException
import io.micronaut.kotlin.processing.getBinaryName
import java.util.*
import java.util.function.Function
import java.util.stream.Stream
internal open class KotlinClassElement(
private val nativeType: KotlinClassNativeElement,
elementAnnotationMetadataFactory: ElementAnnotationMetadataFactory,
var resolvedTypeArguments: Map?,
visitorContext: KotlinVisitorContext,
private val internalArrayDimensions: Int = 0,
private val typeVariable: Boolean = false
) : AbstractKotlinElement(
nativeType,
elementAnnotationMetadataFactory,
visitorContext
),
ArrayableClassElement {
private val definedType: KSType? by lazy {
nativeType.type
}
val declaration: KSClassDeclaration by lazy {
nativeType.declaration
}
val kotlinType: KSType by lazy {
definedType ?: declaration.asStarProjectedType()
}
private val asType: KotlinClassElement by lazy {
if (definedType == null) {
this
} else {
KotlinClassElement(
KotlinClassNativeElement(declaration), // Strip the kotlin type and the owner
elementAnnotationMetadataFactory,
resolvedTypeArguments,
visitorContext,
arrayDimensions,
typeVariable
)
}
}
private val outerType: KSType? by lazy {
val outerDecl = declaration.parentDeclaration as? KSClassDeclaration
outerDecl?.asType(
kotlinType.arguments.subList(
declaration.typeParameters.size,
kotlinType.arguments.size
)
)
}
private val resolvedProperties: List by lazy {
getBeanProperties(PropertyElementQuery.of(this))
}
private val internalDeclaredGenericPlaceholders: List by lazy {
kotlinType.declaration.typeParameters.map {
resolveTypeParameter(nativeType, it, emptyMap()) as GenericPlaceholderElement
}.toList()
}
private val internalFields: List by lazy {
super.getFields()
}
private val internalMethods: List by lazy {
super.getMethods()
}
private val enclosedElementsQuery = KotlinEnclosedElementsQuery()
private val nativeProperties: List by lazy {
val properties: MutableList = ArrayList()
var clazz: KotlinClassElement? = this
while (clazz != null) {
// We need to aggregate all the hierarchy properties because
// getAllProperties doesn't return correct parent of the property
properties.addAll(clazz.getDeclaredSyntheticBeanProperties())
clazz = clazz.superType.orElse(null) as KotlinClassElement?
}
properties
}
private val declaredNativeProperties: List by lazy {
declaration.getDeclaredProperties()
.filter { !it.isPrivate() }
.map {
KotlinPropertyElement(
this,
it,
elementAnnotationMetadataFactory,
visitorContext
)
}
.filter { !it.hasAnnotation(JvmField::class.java) }
.toList()
}
@OptIn(KspExperimental::class)
private val internalCanonicalName: String by lazy {
val javaName = visitorContext.resolver.mapKotlinNameToJava(declaration.qualifiedName!!)
javaName?.asString() ?: declaration.qualifiedName!!.asString()
}
private val internalName = declaration.getBinaryName(visitorContext.resolver, visitorContext)
private val resolvedInterfaces: Collection by lazy {
declaration.superTypes.map { it.resolve() }
.filter {
it != visitorContext.resolver.builtIns.anyType
}
.filter {
val declaration = it.declaration
declaration is KSClassDeclaration && declaration.classKind == ClassKind.INTERFACE
}.map {
newClassElement(nativeType, it, typeArguments)
}.toList()
}
private val resolvedSuperType: Optional by lazy {
val superType = declaration.superTypes.firstOrNull {
val resolved = it.resolve()
if (resolved == visitorContext.resolver.builtIns.anyType) {
false
} else {
val declaration = resolved.declaration
declaration is KSClassDeclaration && declaration.classKind != ClassKind.INTERFACE
}
}
Optional.ofNullable(superType)
.map {
newClassElement(nativeType, it.resolve(), typeArguments)
}
}
private val resolvedPrimaryConstructor: Optional by lazy {
val primaryConstructor = super.getPrimaryConstructor()
if (primaryConstructor.isPresent) {
primaryConstructor
} else {
Optional.ofNullable(declaration.primaryConstructor)
.filter { !it.isPrivate() }
.map {
visitorContext.elementFactory.newConstructorElement(
this,
it,
elementAnnotationMetadataFactory
)
}
}
}
private val resolvedDefaultConstructor: Optional by lazy {
val defaultConstructor = super.getDefaultConstructor()
if (defaultConstructor.isPresent) {
defaultConstructor
} else {
Optional.ofNullable(declaration.primaryConstructor)
.filter { !it.isPrivate() && it.parameters.isEmpty() }
.map {
visitorContext.elementFactory.newConstructorElement(
this,
it,
elementAnnotationMetadataFactory
)
}
}
}
private val resolvedAnnotationMetadataToWrite: MutableAnnotationMetadataDelegate<*> by lazy {
if (definedType != null) {
resolvedTypeAnnotationMetadata
} else {
super.getAnnotationMetadataToWrite()
}
}
private val resolvedTypeAnnotationMetadata: MutableAnnotationMetadataDelegate by lazy {
if (definedType != null) {
elementAnnotationMetadataFactory.buildTypeAnnotations(this)
} else {
MutableAnnotationMetadataDelegate.EMPTY as MutableAnnotationMetadataDelegate
}
}
private val resolvedAnnotationMetadata: AnnotationMetadata by lazy {
if (definedType != null) {
AnnotationMetadataHierarchy(
true,
super.getAnnotationMetadata(),
typeAnnotationMetadata
)
} else {
super.getAnnotationMetadata()
}
}
override fun getType() = asType
companion object Helper {
fun getType(ref: KSAnnotated, visitorContext: KotlinVisitorContext): KSType {
when (ref) {
is KSType -> {
return ref
}
is KSTypeReference -> {
return ref.resolve()
}
is KSTypeParameter -> {
return ref.bounds.firstOrNull()?.resolve()
?: visitorContext.resolver.builtIns.anyType
}
is KSClassDeclaration -> {
return ref.asStarProjectedType()
}
is KSTypeArgument -> {
val ksType = ref.type?.resolve()
if (ksType != null) {
return ksType
} else {
throw IllegalArgumentException("Unresolvable type argument $ref")
}
}
is KSTypeAlias -> {
return ref.type.resolve()
}
else -> {
throw IllegalArgumentException("Not a type $ref")
}
}
}
}
override fun getName() = internalName
override fun getCanonicalName() = internalCanonicalName
override fun getPackageName() = declaration.packageName.asString()
override fun isDeclaredNullable() = kotlinType.isMarkedNullable
override fun isNullable() = kotlinType.isMarkedNullable
override fun getSyntheticBeanProperties() = nativeProperties
private fun getDeclaredSyntheticBeanProperties() = declaredNativeProperties
override fun getAccessibleStaticCreators(): List {
val staticCreators: MutableList = mutableListOf()
staticCreators.addAll(super.getAccessibleStaticCreators())
return staticCreators.ifEmpty {
val companion = declaration.declarations
.filter { it is KSClassDeclaration && it.isCompanionObject }
.map { it as KSClassDeclaration }
.map { newKotlinClassElement(it, emptyMap()) }
.firstOrNull() ?: return emptyList()
return companion.getEnclosedElements(
ElementQuery.ALL_METHODS
.annotated {
it.hasStereotype(
Creator::class.java
)
}
.modifiers { it.isEmpty() || it.contains(ElementModifier.PUBLIC) }
.filter { method ->
method.returnType.isAssignable(this)
}
)
}
}
override fun getBeanProperties() = resolvedProperties
override fun getDeclaredGenericPlaceholders() = internalDeclaredGenericPlaceholders
override fun getFields() = internalFields
override fun findField(name: String) = Optional.ofNullable(
internalFields.firstOrNull { it.name == name }
)
override fun getMethods() = internalMethods
override fun findMethod(name: String?) = Optional.ofNullable(
internalMethods.firstOrNull { it.name == name }
)
override fun getBeanProperties(propertyElementQuery: PropertyElementQuery): MutableList {
val customReaderPropertyNameResolver =
Function> { Optional.empty() }
val customWriterPropertyNameResolver =
Function> { Optional.empty() }
val accessKinds = propertyElementQuery.accessKinds
val fieldAccess =
accessKinds.contains(BeanProperties.AccessKind.FIELD) && !propertyElementQuery.accessKinds.contains(
BeanProperties.AccessKind.METHOD
)
if (fieldAccess) {
// all kotlin fields are private
return mutableListOf()
}
val eq = ElementQuery.of(PropertyElement::class.java)
.named { n -> !propertyElementQuery.excludes.contains(n) }
.named { n ->
propertyElementQuery.includes.isEmpty() || propertyElementQuery.includes.contains(
n
)
}
.modifiers {
val visibility = propertyElementQuery.visibility
if (visibility == BeanProperties.Visibility.PUBLIC) {
it.contains(ElementModifier.PUBLIC)
} else {
!it.contains(ElementModifier.PRIVATE)
}
}.annotated { prop ->
if (prop.hasAnnotation(JvmField::class.java)) {
false
} else {
val excludedAnnotations = propertyElementQuery.excludedAnnotations
excludedAnnotations.isEmpty() || !excludedAnnotations.any {
prop.hasAnnotation(
it
)
}
}
}
val allProperties: MutableList = mutableListOf()
allProperties.addAll(enclosedElementsQuery.getEnclosedElements(this, eq))
// unfortunate hack since these are not excluded?
if (hasDeclaredStereotype(ConfigurationReader::class.java)) {
val configurationBuilderQuery = ElementQuery.of(PropertyElement::class.java)
.annotated { it.hasDeclaredAnnotation(ConfigurationBuilder::class.java) }
.onlyInstance()
.onlyAccessible(this)
enclosedElementsQuery.getEnclosedElements(this, configurationBuilderQuery)
.forEach { e ->
if (!allProperties.contains(e)) {
allProperties.add(e)
}
}
}
val propertyNames = allProperties.map { it.name }.toSet()
val resolvedProperties: MutableList = mutableListOf()
val methodProperties = AstBeanPropertiesUtils.resolveBeanProperties(propertyElementQuery,
this,
{
getEnclosedElements(
ElementQuery.ALL_METHODS
)
},
{
emptyList()
},
false, propertyNames,
customReaderPropertyNameResolver,
customWriterPropertyNameResolver,
{ value: AstBeanPropertiesUtils.BeanPropertyData ->
if (!value.isExcluded) {
this.mapToPropertyElement(
value
)
} else {
null
}
})
resolvedProperties.addAll(methodProperties)
resolvedProperties.addAll(allProperties)
return resolvedProperties
}
private fun mapToPropertyElement(value: AstBeanPropertiesUtils.BeanPropertyData) =
KotlinSimplePropertyElement(
this@KotlinClassElement,
value.type,
value.propertyName,
value.field,
value.getter,
value.setter,
elementAnnotationMetadataFactory,
visitorContext,
value.isExcluded
)
@OptIn(KspExperimental::class)
override fun getSimpleName(): String {
var parentDeclaration = declaration.parentDeclaration
return if (parentDeclaration == null) {
val qualifiedName = declaration.qualifiedName
if (qualifiedName != null) {
visitorContext.resolver.mapKotlinNameToJava(qualifiedName)?.getShortName()
?: declaration.simpleName.asString()
} else
declaration.simpleName.asString()
} else {
val builder = StringBuilder(declaration.simpleName.asString())
while (parentDeclaration != null) {
builder.insert(0, '$')
.insert(0, parentDeclaration.simpleName.asString())
parentDeclaration = parentDeclaration.parentDeclaration
}
builder.toString()
}
}
override fun getSuperType() = resolvedSuperType
override fun getInterfaces() = resolvedInterfaces
override fun isStatic() = if (isInner) {
// inner classes in Kotlin are by default static unless
// the 'inner' keyword is used
!declaration.modifiers.contains(Modifier.INNER)
} else {
super.isStatic()
}
override fun isInterface() = declaration.classKind == ClassKind.INTERFACE
override fun isTypeVariable() = typeVariable
override fun isAssignable(type: String): Boolean {
val otherDeclaration = visitorContext.resolver.getClassDeclarationByName(type)
if (otherDeclaration != null) {
if (declaration == otherDeclaration) {
return true
}
val thisFullName = declaration.getBinaryName(
visitorContext.resolver,
visitorContext
)
val otherFullName = otherDeclaration.getBinaryName(
visitorContext.resolver,
visitorContext
)
if (thisFullName == otherFullName) {
return true
}
val otherKotlinType = otherDeclaration.asStarProjectedType().makeNullable()
val kotlinTypeNullable = kotlinType.makeNullable()
if (otherKotlinType == kotlinTypeNullable) {
return true
}
if (otherKotlinType.isAssignableFrom(kotlinTypeNullable)) {
return true
}
}
return isAssignable2(type)
}
// Second attempt to check if the class is assignable, the method is public for testing
@OptIn(KspExperimental::class)
fun isAssignable2(type: String): Boolean {
val kotlinName = visitorContext.resolver.mapJavaNameToKotlin(
visitorContext.resolver.getKSNameFromString(type)
) ?: return false
val kotlinClassByName = visitorContext.resolver.getKotlinClassByName(kotlinName) ?: return false
return kotlinClassByName.asStarProjectedType().makeNullable().isAssignableFrom(kotlinType.starProjection().makeNullable())
}
override fun isAssignable(type: ClassElement): Boolean {
if (type is KotlinClassElement) {
return type.kotlinType.starProjection().makeNullable().isAssignableFrom(kotlinType.starProjection().makeNullable())
}
return super.isAssignable(type)
}
override fun copyThis() = KotlinClassElement(
nativeType,
elementAnnotationMetadataFactory,
resolvedTypeArguments,
visitorContext,
arrayDimensions,
typeVariable
)
override fun withTypeArguments(typeArguments: Map) = KotlinClassElement(
nativeType,
elementAnnotationMetadataFactory,
typeArguments,
visitorContext,
arrayDimensions,
typeVariable
)
@NonNull
override fun withTypeArguments(@NonNull typeArguments: Collection): ClassElement? {
if (getTypeArguments() == typeArguments) {
return this
}
if (typeArguments.isEmpty()) {
return withTypeArguments(emptyMap())
}
val boundByName: MutableMap = LinkedHashMap()
val keys = getTypeArguments().keys
val variableNames: Iterator = keys.iterator()
val args = typeArguments.iterator()
while (variableNames.hasNext() && args.hasNext()) {
var next = args.next()
val nativeType = next.nativeType
if (nativeType is Class<*>) {
next = visitorContext.getClassElement(nativeType).orElse(next)
}
if (nativeType is String) {
next = visitorContext.getClassElement(nativeType).orElse(next)
}
boundByName[variableNames.next()] = next
}
return withTypeArguments(boundByName)
}
override fun isAbstract(): Boolean = declaration.isAbstract()
override fun withAnnotationMetadata(annotationMetadata: AnnotationMetadata) =
super.withAnnotationMetadata(annotationMetadata) as ClassElement
override fun isArray() = arrayDimensions > 0
override fun getArrayDimensions() = internalArrayDimensions
override fun withArrayDimensions(arrayDimensions: Int) = KotlinClassElement(
nativeType,
elementAnnotationMetadataFactory,
resolvedTypeArguments,
visitorContext,
arrayDimensions,
typeVariable
)
override fun isInner() = outerType != null
override fun getPrimaryConstructor() = resolvedPrimaryConstructor
override fun getDefaultConstructor() = resolvedDefaultConstructor
override fun getTypeArguments(): Map {
if (resolvedTypeArguments == null) {
val ksDeclaration = kotlinType.declaration
resolvedTypeArguments = if (ksDeclaration is KSTypeParameter) {
resolveTypeArguments(
nativeType,
ksDeclaration.bounds.toList()[0].resolve(),
emptyMap()
)
} else if (definedType != null) {
resolveTypeArguments(nativeType, definedType!!, emptyMap())
} else {
resolveTypeArguments(nativeType, declaration, emptyMap())
}
}
return resolvedTypeArguments!!
}
override fun getEnclosingType(): Optional {
if (isInner) {
return Optional.of(
newClassElement(nativeType, outerType!!, emptyMap())
)
}
return Optional.empty()
}
override fun getAnnotationMetadataToWrite() = resolvedAnnotationMetadataToWrite
override fun getAnnotationMetadata() = resolvedAnnotationMetadata
override fun getTypeAnnotationMetadata() = resolvedTypeAnnotationMetadata
override fun getEnclosedElements(query: ElementQuery): List =
enclosedElementsQuery.getEnclosedElements(this, query)
private inner class KotlinEnclosedElementsQuery :
EnclosedElementsQuery() {
@OptIn(KspExperimental::class)
override fun getElementName(element: KSNode): String {
if (element is KSFunctionDeclaration) {
return visitorContext.resolver.getJvmName(element)!!
}
if (element is KSDeclaration) {
return element.getBinaryName(visitorContext.resolver, visitorContext)
}
return ""
}
override fun getNativeClassType(classElement: ClassElement): KSClassDeclaration {
return (classElement as KotlinClassElement).nativeType.declaration
}
override fun getNativeType(element: Element): KSNode {
return (element as AbstractKotlinElement<*>).nativeType.element
}
override fun getExcludedNativeElements(result: ElementQuery.Result<*>): Set {
if (result.isExcludePropertyElements) {
val excludeElements: MutableSet = HashSet()
for (excludePropertyElement in beanProperties) {
excludePropertyElement.readMethod.ifPresent { methodElement: MethodElement ->
excludeElements.add(
getNativeType(methodElement)
)
}
excludePropertyElement.writeMethod.ifPresent { methodElement: MethodElement ->
excludeElements.add(
getNativeType(methodElement)
)
}
excludePropertyElement.field.ifPresent { fieldElement: FieldElement ->
excludeElements.add(
getNativeType(fieldElement)
)
}
}
return excludeElements
}
return emptySet()
}
override fun getSuperClass(classNode: KSClassDeclaration): KSClassDeclaration? {
val superTypes = classNode.superTypes
for (superclass in superTypes) {
val resolved = superclass.resolve()
val declaration = resolved.declaration
if (declaration is KSClassDeclaration) {
if (declaration.classKind == ClassKind.CLASS && declaration.qualifiedName?.asString() != Any::class.qualifiedName) {
return declaration
}
}
}
return null
}
override fun getInterfaces(classDeclaration: KSClassDeclaration): Collection {
val superTypes = classDeclaration.superTypes
val result: MutableCollection = ArrayList()
for (superclass in superTypes) {
val resolved = superclass.resolve()
val declaration = resolved.declaration
if (declaration is KSClassDeclaration) {
if (declaration.classKind == ClassKind.INTERFACE) {
result.add(declaration)
}
}
}
return result
}
override fun getEnclosedElements(
classNode: KSClassDeclaration,
result: ElementQuery.Result<*>
): List {
val elementType: Class<*> = result.elementType
return getEnclosedElements(classNode, result, elementType)
}
private fun getEnclosedElements(
classNode: KSClassDeclaration,
result: ElementQuery.Result<*>,
elementType: Class<*>
): List {
return when (elementType) {
MemberElement::class.java -> {
Stream.concat(
getEnclosedElements(classNode, result, FieldElement::class.java).stream(),
getEnclosedElements(classNode, result, MethodElement::class.java).stream()
).toList()
}
MethodElement::class.java -> {
classNode.getDeclaredFunctions()
.filter { func: KSFunctionDeclaration ->
!func.isConstructor() &&
func.origin != Origin.SYNTHETIC &&
// this is a hack but no other way it seems
!listOf(
"hashCode",
"toString",
"equals"
).contains(func.simpleName.asString())
}
.toList()
}
FieldElement::class.java -> {
classNode.getDeclaredProperties()
.filter {
it.hasBackingField &&
it.origin != Origin.SYNTHETIC
}
.toList()
}
PropertyElement::class.java -> {
classNode.getDeclaredProperties().toList()
}
ConstructorElement::class.java -> {
classNode.getConstructors().toList()
}
ClassElement::class.java -> {
classNode.declarations.filter {
it is KSClassDeclaration
}.toList()
}
else -> {
throw java.lang.IllegalStateException("Unknown result type: $elementType")
}
}
}
override fun excludeClass(classNode: KSClassDeclaration): Boolean {
val t = classNode.asStarProjectedType()
val builtIns = visitorContext.resolver.builtIns
return t == builtIns.anyType ||
t == builtIns.nothingType ||
t == builtIns.unitType ||
classNode.qualifiedName.toString() == Enum::class.java.name
}
override fun toAstElement(
nativeType: KSNode,
elementType: Class<*>
): Element {
val elementFactory: KotlinElementFactory = visitorContext.elementFactory
val owningClass = this@KotlinClassElement
return when (nativeType) {
is KSFunctionDeclaration -> {
if (nativeType.isConstructor()) {
return elementFactory.newConstructorElement(
owningClass,
nativeType,
elementAnnotationMetadataFactory
)
} else {
return elementFactory.newMethodElement(
owningClass,
nativeType,
elementAnnotationMetadataFactory
)
}
}
is KSPropertyDeclaration -> {
if (elementType == PropertyElement::class.java) {
val prop = KotlinPropertyElement(
owningClass,
nativeType,
elementAnnotationMetadataFactory, visitorContext
)
if (!prop.hasAnnotation(JvmField::class.java)) {
return prop
} else {
return elementFactory.newFieldElement(
owningClass,
nativeType,
elementAnnotationMetadataFactory
)
}
} else {
return elementFactory.newFieldElement(
owningClass,
nativeType,
elementAnnotationMetadataFactory
)
}
}
is KSClassDeclaration -> newKotlinClassElement(
nativeType,
emptyMap()
)
else -> throw ProcessingException(owningClass, "Unexpected element: $nativeType")
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy