main.name.remal.gradle_plugins.plugins.kotlin.KotlinJava8DefaultMethodsPlugin.kt Maven / Gradle / Ivy
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