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

io.micronaut.kotlin.processing.visitor.KotlinVisitorContext.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.KspExperimental
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSNode
import io.micronaut.core.convert.ArgumentConversionContext
import io.micronaut.core.convert.value.MutableConvertibleValues
import io.micronaut.core.convert.value.MutableConvertibleValuesMap
import io.micronaut.core.reflect.ClassUtils
import io.micronaut.core.reflect.ReflectionUtils
import io.micronaut.core.util.StringUtils
import io.micronaut.expressions.context.DefaultExpressionCompilationContextFactory
import io.micronaut.expressions.context.ExpressionCompilationContextFactory
import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder
import io.micronaut.inject.ast.ClassElement
import io.micronaut.inject.ast.Element
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory
import io.micronaut.inject.visitor.VisitorContext
import io.micronaut.inject.writer.GeneratedFile
import io.micronaut.kotlin.processing.KotlinNativeElementsHelper
import io.micronaut.kotlin.processing.KotlinOutputVisitor
import io.micronaut.kotlin.processing.annotation.KotlinAnnotationMetadataBuilder
import io.micronaut.kotlin.processing.annotation.KotlinElementAnnotationMetadataFactory
import java.io.*
import java.net.URI
import java.nio.file.Files
import java.util.*
import java.util.function.BiConsumer
import kotlin.collections.ArrayList

@OptIn(KspExperimental::class)
internal class KotlinVisitorContext(
    private val environment: SymbolProcessorEnvironment,
    var resolver: Resolver
) : VisitorContext {

    private val visitorAttributes: MutableConvertibleValues = MutableConvertibleValuesMap()
    private val elementFactory: KotlinElementFactory = KotlinElementFactory(this)
    private val outputVisitor = KotlinOutputVisitor(environment, this)
    val annotationMetadataBuilder = KotlinAnnotationMetadataBuilder(environment, resolver, this)
    private val elementAnnotationMetadataFactory = KotlinElementAnnotationMetadataFactory(false, annotationMetadataBuilder)
    private val expressionCompilationContextFactory = DefaultExpressionCompilationContextFactory(this)
    val nativeElementsHelper = KotlinNativeElementsHelper(resolver)
    var aggregating: Boolean = false
    init {
        try {
            // Workaround for bug in KSP https://github.com/google/ksp/issues/1493
            val resolverImplClass = ClassUtils.forName("com.google.devtools.ksp.processing.impl.ResolverImpl", javaClass.classLoader).orElseThrow()
            val kotlinTypeMapperClass = ClassUtils.forName("org.jetbrains.kotlin.codegen.state.KotlinTypeMapper", javaClass.classLoader).orElseThrow()
            val kotlinTypeMapperInstance = ReflectionUtils.getFieldValue(resolverImplClass, "typeMapper", resolver).orElseThrow()
            ReflectionUtils.setField(kotlinTypeMapperClass, "useOldManglingRulesForFunctionAcceptingInlineClass", kotlinTypeMapperInstance, false)
        } catch (e: Exception) {
            // Ignore
        }
    }

    fun updateResolver(resolver: Resolver) {
        this.resolver = resolver
        annotationMetadataBuilder.resolver = resolver
        nativeElementsHelper.resolver = resolver
    }

    override fun getLanguage() = VisitorContext.Language.KOTLIN

    override fun  get(
        name: CharSequence?,
        conversionContext: ArgumentConversionContext?
    ): Optional {
        return visitorAttributes.get(name, conversionContext)
    }

    override fun names(): MutableSet {
        return visitorAttributes.names()
    }

    override fun values(): MutableCollection {
        return visitorAttributes.values()
    }

    override fun put(key: CharSequence?, value: Any?): MutableConvertibleValues {
        visitorAttributes.put(key, value)
        return this
    }

    override fun remove(key: CharSequence?): MutableConvertibleValues {
        visitorAttributes.remove(key)
        return this
    }

    override fun clear(): MutableConvertibleValues {
        visitorAttributes.clear()
        return this
    }

    override fun getClassElement(name: String): Optional {
        var declaration = resolver.getClassDeclarationByName(name)
        if (declaration == null) {
            declaration = resolver.getClassDeclarationByName(name.replace('$', '.'))
        }
        return Optional.ofNullable(declaration)
            .map(elementFactory::newClassElement)
    }

    @OptIn(KspExperimental::class)
    override fun getClassElements(
        aPackage: String,
        vararg stereotypes: String
    ): Array {
        return resolver.getDeclarationsFromPackage(aPackage)
            .filterIsInstance()
            .filter { declaration ->
                declaration.annotations.any { ann ->
                    stereotypes.contains(
                        KotlinAnnotationMetadataBuilder.getAnnotationTypeName(
                            resolver,
                            ann,
                            this
                        )
                    )
                }
            }
            .map { declaration ->
                elementFactory.newClassElement(declaration)
            }
            .toList()
            .toTypedArray()
    }

    override fun getServiceEntries(): MutableMap> {
        return outputVisitor.serviceEntries
    }

    override fun visitClass(classname: String, vararg originatingElements: Element): OutputStream {
        return outputVisitor.visitClass(classname, *originatingElements)
    }

    override fun visitServiceDescriptor(type: String, classname: String) {
        outputVisitor.visitServiceDescriptor(type, classname)
    }

    override fun visitServiceDescriptor(
        type: String,
        classname: String,
        originatingElement: Element
    ) {
        outputVisitor.visitServiceDescriptor(type, classname, originatingElement)
    }

    override fun visitMetaInfFile(
        path: String,
        vararg originatingElements: Element
    ): Optional {
        return outputVisitor.visitMetaInfFile(path, *originatingElements)
    }

    override fun visitGeneratedFile(path: String): Optional {
        return outputVisitor.visitGeneratedFile(path)
    }

    override fun visitGeneratedFile(path: String, vararg originatingElements: Element): Optional {
        return outputVisitor.visitGeneratedFile(path, *originatingElements)
    }

    override fun visitGeneratedSourceFile(packageName: String, fileNameWithoutExtension: String, vararg originatingElements: Element): Optional {
        return outputVisitor.visitGeneratedSourceFile(packageName, fileNameWithoutExtension, *originatingElements)
    }

    override fun finish() {
        outputVisitor.finish()
    }

    override fun getClassElement(
        name: String,
        annotationMetadataFactory: ElementAnnotationMetadataFactory
    ): Optional {
        var declaration = resolver.getClassDeclarationByName(name)
        if (declaration == null) {
            declaration = resolver.getClassDeclarationByName(name.replace('$', '.'))
        }
        return Optional.ofNullable(declaration)
            .map { elementFactory.newClassElement(it, annotationMetadataFactory) }
    }

    override fun getElementFactory(): KotlinElementFactory = elementFactory

    override fun getElementAnnotationMetadataFactory(): ElementAnnotationMetadataFactory {
        return elementAnnotationMetadataFactory
    }

    override fun getExpressionCompilationContextFactory(): ExpressionCompilationContextFactory {
        return expressionCompilationContextFactory
    }

    override fun getAnnotationMetadataBuilder(): AbstractAnnotationMetadataBuilder<*, *> {
        return annotationMetadataBuilder
    }

    override fun getOptions(): Map {
        return environment.options
    }

    override fun info(message: String, element: Element?) {
        printMessage(message, environment.logger::info, element)
    }

    fun info(message: String, element: KSNode?) {
        printMessage(message, environment.logger::info, element)
    }

    override fun info(message: String) {
        printMessage(message, environment.logger::info, null as KSNode?)
    }

    override fun fail(message: String, element: Element?) {
        printMessage(message, environment.logger::error, element)
    }

    fun fail(message: String, element: KSNode?) {
        printMessage(message, environment.logger::error, element)
    }

    override fun warn(message: String, element: Element?) {
        printMessage(message, environment.logger::warn, element)
    }

    fun warn(message: String, element: KSNode?) {
        printMessage(message, environment.logger::warn, element)
    }

    private fun printMessage(
        message: String,
        logger: BiConsumer,
        element: Element?
    ) {
        if (element is AbstractKotlinElement<*>) {
            val el = element.nativeType.element
            printMessage(message, logger, el)
        } else {
            printMessage(message, logger, null as KSNode?)
        }
    }

    private fun printMessage(
        message: String,
        logger: BiConsumer,
        element: KSNode?
    ) {
        if (StringUtils.isNotEmpty(message)) {
            logger.accept(message, element)
        }
    }

    open class KspGeneratedFile(
        environment: SymbolProcessorEnvironment,
        elements: MutableList,
        dependencies: Dependencies
    ) : GeneratedFile {

        private val file: File

        init {
            val fileName = elements.removeAt(elements.size - 1)
            val prevFiles = ArrayList(environment.codeGenerator.generatedFile)
            environment.codeGenerator.createNewFile(
                dependencies,
                elements.joinToString("."),
                fileName.substringBeforeLast('.'),
                fileName.substringAfterLast('.', "")
            ).close() // Create an empty file first to find the actual file location
            val newFiles = ArrayList(environment.codeGenerator.generatedFile)
            newFiles.removeAll(prevFiles)
            if (newFiles.size != 1) {
                throw IllegalStateException("Expected to find one file!")
            }
            file = newFiles[0]
        }

        override fun toURI(): URI {
            return file.toURI()
        }

        override fun getName(): String {
            return file.name
        }

        override fun openInputStream(): InputStream {
            return Files.newInputStream(file.toPath())
        }

        override fun openOutputStream(): OutputStream {
            return Files.newOutputStream(file.toPath())
        }

        override fun openReader(): Reader {
            return file.reader()
        }

        override fun getTextContent(): CharSequence {
            return file.readText()
        }

        override fun openWriter(): Writer {
            return OutputStreamWriter(openOutputStream())
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy