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

io.micronaut.kotlin.processing.visitor.KotlinVisitorContext.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.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.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.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

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

    private val visitorAttributes: MutableConvertibleValues
    private val elementFactory: KotlinElementFactory
    private val outputVisitor = KotlinOutputVisitor(environment)
    val annotationMetadataBuilder: KotlinAnnotationMetadataBuilder
    private val elementAnnotationMetadataFactory: KotlinElementAnnotationMetadataFactory
    private val expressionCompilationContextFactory : ExpressionCompilationContextFactory

    init {
        visitorAttributes = MutableConvertibleValuesMap()
        annotationMetadataBuilder = KotlinAnnotationMetadataBuilder(environment, resolver, this)
        elementFactory = KotlinElementFactory(this)
        elementAnnotationMetadataFactory =
            KotlinElementAnnotationMetadataFactory(false, annotationMetadataBuilder)
        expressionCompilationContextFactory = DefaultExpressionCompilationContextFactory(this)
    }

    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 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 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)
        }
    }

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

        private val fileName = elements.removeAt(elements.size - 1)
        private val path = elements.joinToString(".")
        private val file = File(path)


        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 = environment.codeGenerator.createNewFile(
            dependencies,
            elements.joinToString("."),
            fileName.substringBeforeLast('.'),
            fileName.substringAfterLast('.'))

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

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

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

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy