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

main.name.remal.gradle_plugins.plugins.kotlin.KotlinJava8DefaultMethodsPlugin.kt Maven / Gradle / Ivy

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

import name.remal.CLASS_FILE_NAME_SUFFIX
import name.remal.accept
import name.remal.addAll
import name.remal.default
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.ClassesProcessor.PRIORITIZED_STAGE
import name.remal.gradle_plugins.api.classes_processing.ClassesProcessorsGradleTaskFactory
import name.remal.gradle_plugins.api.classes_processing.ProcessContext
import name.remal.gradle_plugins.dsl.ApplyPluginClasses
import name.remal.gradle_plugins.dsl.BaseReflectiveProjectPlugin
import name.remal.gradle_plugins.dsl.Plugin
import name.remal.gradle_plugins.dsl.WithPlugins
import name.remal.gradle_plugins.dsl.extensions.isPluginApplied
import name.remal.gradle_plugins.dsl.extensions.isPluginAppliedAndNotDisabled
import name.remal.gradle_plugins.dsl.extensions.logWarn
import name.remal.gradle_plugins.plugins.classes_processing.ClassesProcessingPlugin
import name.remal.toLoadVarInsns
import name.remal.toReturnInsn
import name.remal.version.Version
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Opcodes.ACC_ABSTRACT
import org.objectweb.asm.Opcodes.ACC_INTERFACE
import org.objectweb.asm.Opcodes.ACC_STATIC
import org.objectweb.asm.Opcodes.ALOAD
import org.objectweb.asm.Opcodes.INVOKESTATIC
import org.objectweb.asm.Type.getArgumentTypes
import org.objectweb.asm.Type.getReturnType
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.VarInsnNode
import org.slf4j.LoggerFactory

@Plugin(
    id = "name.remal.kotlin-java8-default-methods",
    description = "This plugin processes Kotlin class files and make Kotlin default interface methods as JVM default methods",
    tags = ["kotlin", "kotlin-java8", "kotlin-default-methods"]
)
@WithPlugins(KotlinJvmPluginId::class)
@ApplyPluginClasses(KotlinJvmSettingsPlugin::class, ClassesProcessingPlugin::class)
class KotlinJava8DefaultMethodsPlugin : BaseReflectiveProjectPlugin()


class KotlinJava8DefaultMethodsClassesProcessor : ClassesProcessor {

    companion object {
        @JvmStatic
        private val logger = LoggerFactory.getLogger(KotlinJava8DefaultMethodsClassesProcessor::class.java)
    }

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

        if (0 == (ACC_INTERFACE and classNode.access)) return

        val abstractMethodNodes = classNode.methods.default(emptyList()).filter {
            if (0 != (ACC_STATIC and it.access)) return@filter false // skip static methods
            if (0 == (ACC_ABSTRACT and it.access)) return@filter false // skip not abstract methods
            return@filter true
        }

        if (abstractMethodNodes.isEmpty()) return

        val defaultImplsClassNode: ClassNode = run {
            val defaultImplsClassInternalName = classNode.name + "\$DefaultImpls"
            val defaultImplsBytecode = context.readBinaryResource(defaultImplsClassInternalName + CLASS_FILE_NAME_SUFFIX) ?: return
            return@run ClassNode().also { ClassReader(defaultImplsBytecode).accept(it) }
        }

        var isChanged = false
        abstractMethodNodes.forEach { methodNode ->
            val implMethodNode = defaultImplsClassNode.methods?.firstOrNull {
                (ACC_STATIC and it.access) != 0
                    && it.name == methodNode.name
                    && it.desc == "(L${classNode.name};" + methodNode.desc.substring(1)
            } ?: return@forEach

            logger.debug(
                "Making {}.{}{} method default method that calls {}.{}{}",
                classNode.name, methodNode.name, methodNode.desc,
                defaultImplsClassNode.name, implMethodNode.name, implMethodNode.desc
            )

            methodNode.access = methodNode.access xor ACC_ABSTRACT
            methodNode.instructions = InsnList().also {
                it.add(LabelNode())
                it.add(VarInsnNode(ALOAD, 0))
                it.addAll(getArgumentTypes(methodNode.desc).toLoadVarInsns(1))
                it.add(MethodInsnNode(INVOKESTATIC, defaultImplsClassNode.name, implMethodNode.name, implMethodNode.desc, false))
                it.add(getReturnType(methodNode.desc).toReturnInsn())
            }
            methodNode.maxLocals = 1
            methodNode.maxStack = 1

            isChanged = true
        }

        if (isChanged) {
            val classWriter = ClassWriter(classReader, COMPUTE_MAXS)
            classNode.accept(classWriter)
            bytecodeModifier.modify(classWriter.toByteArray())
        }
    }

    override fun getStage() = PRIORITIZED_STAGE

}

@AutoService
class KotlinJava8DefaultMethodsClassesProcessorFactory : ClassesProcessorsGradleTaskFactory {
    override fun createClassesProcessors(compileTask: AbstractCompile): List {
        if (!compileTask.project.isPluginApplied(KotlinJvmPluginId)) return emptyList()
        if (!compileTask.project.isPluginAppliedAndNotDisabled(KotlinJava8DefaultMethodsPlugin::class.java)) return emptyList()
        if (compileTask !is KotlinCompile) return emptyList()
        val jvmTargetStr: String? = compileTask.kotlinOptions.jvmTarget
        val jvmTarget = Version.parseOrNull(jvmTargetStr)
        if (jvmTarget == null) {
            compileTask.logWarn("${KotlinJava8DefaultMethodsPlugin::class.java.name}: Unsupported value of kotlinOptions.jvmTarget: '$jvmTargetStr'")
            return emptyList()
        }
        if (jvmTarget < Version.create(1, 8)) return emptyList()
        return listOf(KotlinJava8DefaultMethodsClassesProcessor())
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy