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

io.p8e.annotations.processor.ContractProcessor.kt Maven / Gradle / Ivy

package io.p8e.annotations.processor

import com.google.protobuf.Message
import io.p8e.annotations.Fact
import io.p8e.annotations.Input
import io.p8e.annotations.processor.P8eAnnotations.PREREQUISITE
import io.p8e.annotations.processor.P8eAnnotations.FUNCTION
import io.p8e.annotations.processor.P8eAnnotations.FACT
import io.p8e.annotations.processor.P8eAnnotations.INPUT
import io.p8e.annotations.processor.P8eAnnotations.PARTICIPANT
import io.p8e.spec.P8eContract
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.annotation.processing.SupportedAnnotationTypes
import javax.annotation.processing.SupportedSourceVersion
import javax.lang.model.SourceVersion
import javax.lang.model.SourceVersion.RELEASE_11
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind.METHOD
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier
import javax.lang.model.element.Modifier.FINAL
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic.Kind.ERROR
import javax.tools.Diagnostic.Kind.NOTE
import javax.tools.Diagnostic.Kind.WARNING

@SupportedAnnotationTypes("io.p8e.annotations.*")
@SupportedSourceVersion(RELEASE_11)
open class ContractProcessor: AbstractProcessor() {

    override fun process(
        annotations: MutableSet,
        roundEnv: RoundEnvironment
    ): Boolean {
        val annotationMap = P8eAnnotations.values().map { it.clazz to roundEnv.getElementsAnnotatedWith(it.clazz) }.toMap()
        val elementUtil = processingEnv.elementUtils
        val typeUtil = processingEnv.typeUtils

        // Handle our contracts
        annotationMap[PARTICIPANT.clazz]?.let { elements ->
            elements.forEach { element ->
                handleContract(
                    elementUtil,
                    typeUtil,
                    element
                )
            }
        }

        return false
    }

    override fun getSupportedSourceVersion(): SourceVersion {
        return RELEASE_11
    }

    override fun getSupportedAnnotationTypes(): MutableSet {
        return mutableSetOf("io.p8e.annotations.*")
    }

    private fun info(message: String, element: Element) {
        processingEnv.messager.printMessage(NOTE, message, element)
    }

    private fun warn(message: String, element: Element) {
        processingEnv.messager.printMessage(WARNING, message, element)
    }

    private fun error(message: String, element: Element) {
        processingEnv.messager.printMessage(ERROR, message, element)
    }

    private fun handleContract(
        elements: Elements,
        types: Types,
        element: Element
    ) {
        val p8eContractType = elements.getTypeElement(P8eContract::class.java.name).asType()
        require(types.isAssignable(element.asType(), p8eContractType)) {
            "${element.simpleName} must extend the $p8eContractType abstract class." to element
        }
        require(!element.modifiers.contains(FINAL)) {
            "${element.simpleName} must not be marked final to allow for proxying." to element
        }

        val prerequisites = element.enclosedElements
            .filter { it.kind == METHOD && it.getAnnotation(PREREQUISITE.clazz) != null }
            .map { it as ExecutableElement }
            .also { methods ->
                methods.forEach { method ->
                    handleConsideration(
                        elements,
                        types,
                        method
                    )
                }
            }

        val considerations = element.enclosedElements
            .filter { it.kind == METHOD && it.getAnnotation(FUNCTION.clazz) != null }
            .map { it as ExecutableElement }
            .also { methods ->
                methods.forEach { method ->
                    handleConsideration(
                        elements,
                        types,
                        method
                    )
                }
            }

        val allOutputMethods = (prerequisites + considerations)

        val facts = allOutputMethods
            .mapNotNull { it.getAnnotation(FACT.clazz) as? Fact }
        val factNames = facts.map { it.name }
            .toSet()

        require(factNames.size == facts.size) {
            "${element.simpleName} methods annotated with ${FACT.clazz.name} must have unique names in the annotation." to element
        }
    }

    private fun handleConsideration(
        elements: Elements,
        types: Types,
        element: ExecutableElement
    ) {
        val returnType = element.returnType
        val messageType = elements.getTypeElement(Message::class.java.name).asType()
        require(returnType == null || types.isAssignable(returnType, messageType)) {
            "${ElementUtil.printMethodSignature(element)} return type must implement $messageType." to element
        }

        require(!element.modifiers.contains(Modifier.FINAL)) {
            "${ElementUtil.printMethodSignature(element)} must not be final." to element
        }

        val inputs = element.parameters
            .mapNotNull { it.getAnnotation(INPUT.clazz) as? Input }
        val inputNames = inputs.map { it.name }
            .toSet()

        require(inputNames.size == inputs.size) {
            "${ElementUtil.printMethodSignature(element)} parameters annotated with ${INPUT.clazz.name} must have unique names." to element
        }

    }

    private fun require(condition: Boolean, messageProvider: () -> Pair) {
        if (!condition) {
            val (message, element) = messageProvider()
            error(message, element)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy