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

name.remal.gradle_plugins.plugins.environment_variables.EnvironmentVariableClassesProcessor.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package name.remal.gradle_plugins.plugins.environment_variables

import name.remal.*
import name.remal.gradle_plugins.api.AutoService
import name.remal.gradle_plugins.api.classes_processing.BytecodeModifier
import name.remal.gradle_plugins.api.classes_processing.ClassesProcessor
import name.remal.gradle_plugins.api.classes_processing.ClassesProcessorsGradleTaskFactory
import name.remal.gradle_plugins.api.classes_processing.ProcessContext
import name.remal.gradle_plugins.dsl.*
import name.remal.gradle_plugins.dsl.EnvironmentVariable.EnvironmentVariables
import name.remal.gradle_plugins.dsl.extensions.javaPackageName
import name.remal.gradle_plugins.dsl.extensions.toHasEntries
import name.remal.gradle_plugins.dsl.utils.forEachAnnotationNode
import org.gradle.api.Project
import org.gradle.api.tasks.compile.AbstractCompile
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
import kotlin.reflect.jvm.javaMethod

class EnvironmentVariableClassesProcessor(val project: Project) : ClassesProcessor {

    companion object {
        private val notDefinedPluginType: Type = getType(NotDefinedProjectPlugin::class.java)
        private val notDefinedEnvironmentVariableConditionType: Type = getType(NotDefinedEnvironmentVariableCondition::class.java)
        private val pluginDesc: String = getDescriptor(Plugin::class.java)
        private val outerClassInternalNameRegex = Regex("\\\$[^\$/]*\$")
    }

    private data class FallbackPluginInfo(
        val pluginId: String,
        val pluginType: Type
    )

    @Suppress("ComplexMethod", "LongMethod")
    override fun process(bytecode: ByteArray, bytecodeModifier: BytecodeModifier, className: String, resourceName: String, context: ProcessContext) {
        val classNode = ClassNode().also { ClassReader(bytecode).accept(it) }

        val envVarNodes = buildList {
            fun AnnotationNode.processEnvironmentVariable() {
                this[EnvironmentVariable::description].nullIfEmpty().let { this[EnvironmentVariable::description] = it }
                this[EnvironmentVariable::pluginId].nullIfEmpty().let { this[EnvironmentVariable::pluginId] = it }
                this[EnvironmentVariable::pluginClass].nullIf { this == notDefinedPluginType }.let { this[EnvironmentVariable::pluginClass] = it }
                this[EnvironmentVariable::scope].nullIfEmpty().let { this[EnvironmentVariable::scope] = it }
                this[EnvironmentVariable::conditionClass].nullIf { this == notDefinedEnvironmentVariableConditionType }.let { this[EnvironmentVariable::conditionClass] = it }
            }

            forEachAnnotationNode(classNode, EnvironmentVariable::class.java) {
                it.processEnvironmentVariable()
                add(it)
            }

            forEachAnnotationNode(classNode, EnvironmentVariables::class.java) { envVars ->
                envVars[EnvironmentVariables::description].nullIfEmpty().let { envVars[EnvironmentVariables::description] = it }
                envVars[EnvironmentVariables::pluginId].nullIfEmpty().let { envVars[EnvironmentVariables::pluginId] = it }
                envVars[EnvironmentVariables::pluginClass].nullIf { this == notDefinedPluginType }.let { envVars[EnvironmentVariables::pluginClass] = it }
                envVars[EnvironmentVariables::scope].nullIfEmpty().let { envVars[EnvironmentVariables::scope] = it }
                envVars[EnvironmentVariables::conditionClass].nullIf { this == notDefinedEnvironmentVariableConditionType }.let { envVars[EnvironmentVariables::conditionClass] = it }

                @Suppress("UNCHECKED_CAST")
                (envVars[EnvironmentVariables::value] as? List).nullIfEmpty()?.forEach { envVar ->
                    envVar.processEnvironmentVariable()

                    envVar[EnvironmentVariable::description] = envVar[EnvironmentVariable::description] ?: envVars[EnvironmentVariables::description]
                    envVar[EnvironmentVariable::pluginId] = envVar[EnvironmentVariable::pluginId] ?: envVars[EnvironmentVariables::pluginId]
                    envVar[EnvironmentVariable::pluginClass] = envVar[EnvironmentVariable::pluginClass] ?: envVars[EnvironmentVariables::pluginClass]
                    envVar[EnvironmentVariable::scope] = envVar[EnvironmentVariable::scope] ?: envVars[EnvironmentVariables::scope]
                    envVar[EnvironmentVariable::conditionClass] = envVar[EnvironmentVariable::conditionClass] ?: envVars[EnvironmentVariables::conditionClass]

                    add(envVar)
                }
            }

            if (isEmpty()) return
        }

        val fallbackPluginInfo: FallbackPluginInfo? = run {
            if (envVarNodes.all { null != it[EnvironmentVariable::pluginId] }) return@run null

            var currentClassNode = classNode
            while (true) {
                val pluginAnnotationNode = currentClassNode.visibleAnnotations?.firstOrNull { it.desc == pluginDesc } ?: currentClassNode.invisibleAnnotations?.firstOrNull { it.desc == pluginDesc }
                if (pluginAnnotationNode != null) {
                    val pluginId = pluginAnnotationNode[Plugin::id]
                    if (pluginId != null) {
                        return@run FallbackPluginInfo(pluginId, getType("L${currentClassNode.name};"))
                    }
                }

                val outerClassInternalName = outerClassInternalNameRegex.replace(currentClassNode.name, "")
                if (outerClassInternalName == currentClassNode.name) break
                val outerClassResourceName = outerClassInternalName + CLASS_FILE_NAME_SUFFIX
                val outerClassBytecode = context.readBinaryResource(outerClassResourceName) ?: break
                currentClassNode = ClassNode().also { ClassReader(outerClassBytecode).accept(it) }
            }
            return@run null
        }

        envVarNodes.forEach { envVarNode ->
            val infoClassName = buildString {
                append(project.javaPackageName)
                append(".").append(EnvironmentVariableInfo::class.java.simpleName)
                append("$$").append(sha256(arrayOf(
                    classNode.name,
                    envVarNode[EnvironmentVariable::value],
                    envVarNode[EnvironmentVariable::pluginId] ?: fallbackPluginInfo?.pluginId,
                    envVarNode[EnvironmentVariable::pluginClass] ?: fallbackPluginInfo?.pluginType,
                    envVarNode[EnvironmentVariable::scope]
                ).joinToString(":")))
            }
            writeInfoClass(infoClassName, envVarNode, fallbackPluginInfo, context)
            context.writeService(EnvironmentVariableInfo::class.java, infoClassName)
        }
    }

    @Suppress("ComplexMethod", "LongMethod")
    private fun writeInfoClass(infoClassName: String, envVarNode: AnnotationNode, fallbackPluginInfo: FallbackPluginInfo?, context: ProcessContext) {
        val classNode = ClassNode()
        classNode.version = V1_8
        classNode.access = ACC_PUBLIC
        classNode.name = infoClassName.replace('.', '/')
        classNode.superName = getInternalName(Any::class.java)
        classNode.interfaces = mutableListOf(getInternalName(EnvironmentVariableInfo::class.java))
        classNode.methods = mutableListOf()

        classNode.methods.add(MethodNode(ACC_PUBLIC, "", "()V", null, null).also { method ->
            method.instructions = InsnList().also {
                it.add(LabelNode())
                it.add(VarInsnNode(ALOAD, 0))
                it.add(MethodInsnNode(INVOKESPECIAL, classNode.superName, method.name, method.desc, false))
                it.add(InsnNode(RETURN))
            }
            method.maxLocals = 1
            method.maxStack = 1
        })

        fun Any?.toInsnNode(): AbstractInsnNode {
            return if (this == null) InsnNode(ACONST_NULL) else LdcInsnNode(this)
        }

        run {
            val javaMethod = EnvironmentVariableInfo::getVariableName.javaMethod!!
            classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                it.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add(envVarNode[EnvironmentVariable::value].toInsnNode())
                    it.add(InsnNode(ARETURN))
                }
                it.maxLocals = 1
                it.maxStack = 1
            })
        }

        run {
            val javaMethod = EnvironmentVariableInfo::getDescription.javaMethod!!
            classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                it.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add(envVarNode[EnvironmentVariable::description].toInsnNode())
                    it.add(InsnNode(ARETURN))
                }
                it.maxLocals = 1
                it.maxStack = 1
            })
        }

        run {
            val javaMethod = EnvironmentVariableInfo::getPluginId.javaMethod!!
            classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                it.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add((envVarNode[EnvironmentVariable::pluginId] ?: fallbackPluginInfo?.pluginId).toInsnNode())
                    it.add(InsnNode(ARETURN))
                }
                it.maxLocals = 1
                it.maxStack = 1
            })
        }

        run {
            val javaMethod = EnvironmentVariableInfo::getPluginClass.javaMethod!!
            classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                it.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add((envVarNode[EnvironmentVariable::pluginClass] ?: fallbackPluginInfo?.pluginType).toInsnNode())
                    it.add(InsnNode(ARETURN))
                }
                it.maxLocals = 1
                it.maxStack = 1
            })
        }

        run {
            val javaMethod = EnvironmentVariableInfo::getScope.javaMethod!!
            classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                it.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add(envVarNode[EnvironmentVariable::scope].toInsnNode())
                    it.add(InsnNode(ARETURN))
                }
                it.maxLocals = 1
                it.maxStack = 1
            })
        }

        run {
            val javaMethod = EnvironmentVariableInfo::getConditionClass.javaMethod!!
            classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                it.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add(envVarNode[EnvironmentVariable::conditionClass].toInsnNode())
                    it.add(InsnNode(ARETURN))
                }
                it.maxLocals = 1
                it.maxStack = 1
            })
        }

        val classWriter = ClassWriter(0)
        classNode.accept(classWriter)
        context.writeBinaryResource(classNode.name + CLASS_FILE_NAME_SUFFIX, classWriter.toByteArray())
    }

}

@AutoService
class EnvironmentVariableClassesProcessorFactory : ClassesProcessorsGradleTaskFactory {

    override fun createClassesProcessors(compileTask: AbstractCompile): List {
        if (!compileTask.classpath.toHasEntries().containsClass(EnvironmentVariable::class.java)) return emptyList()
        return listOf(EnvironmentVariableClassesProcessor(compileTask.project))
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy