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

name.remal.gradle_plugins.dsl.extensions.org.gradle.api.reporting.ReportContainer.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package name.remal.gradle_plugins.dsl.extensions

import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import com.google.common.reflect.TypeToken
import name.remal.accept
import name.remal.asClass
import name.remal.findConstructor
import name.remal.fromUpperCamelToLowerCamel
import name.remal.toInsnNode
import name.remal.tryLoadClass
import name.remal.uncheckedCast
import org.gradle.api.Task
import org.gradle.api.reporting.Report
import org.gradle.api.reporting.ReportContainer
import org.gradle.api.reporting.SingleFileReport
import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport
import org.gradle.api.reporting.internal.TaskReportContainer
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Opcodes.AASTORE
import org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.objectweb.asm.Opcodes.ALOAD
import org.objectweb.asm.Opcodes.ANEWARRAY
import org.objectweb.asm.Opcodes.ARETURN
import org.objectweb.asm.Opcodes.CHECKCAST
import org.objectweb.asm.Opcodes.DUP
import org.objectweb.asm.Opcodes.GETSTATIC
import org.objectweb.asm.Opcodes.ICONST_0
import org.objectweb.asm.Opcodes.ICONST_1
import org.objectweb.asm.Opcodes.ICONST_2
import org.objectweb.asm.Opcodes.INVOKESPECIAL
import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
import org.objectweb.asm.Opcodes.RETURN
import org.objectweb.asm.Opcodes.V1_8
import org.objectweb.asm.Type.VOID_TYPE
import org.objectweb.asm.Type.getConstructorDescriptor
import org.objectweb.asm.Type.getDescriptor
import org.objectweb.asm.Type.getInternalName
import org.objectweb.asm.Type.getMethodDescriptor
import org.objectweb.asm.Type.getType
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.TypeInsnNode
import org.objectweb.asm.tree.VarInsnNode
import org.objectweb.asm.util.CheckClassAdapter
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import javax.inject.Inject
import kotlin.collections.asSequence
import kotlin.collections.forEach
import kotlin.collections.mutableListOf

private val taskReportContainerClassCache: LoadingCache>, Class>> = CacheBuilder.newBuilder()
    .weakValues()
    .build(object : CacheLoader>, Class>>() {
        @Suppress("LongMethod")
        override fun load(containerClass: Class>): Class> {
            if (!containerClass.isInterface) throw IllegalArgumentException("$containerClass is not an interface")

            val reportSuperType: Class = run {
                val type = TypeToken.of(containerClass).getSupertype(ReportContainer::class.java).type
                if (type !is ParameterizedType) throw IllegalStateException("$type is not instance of ParameterizedType")
                type.actualTypeArguments[0].asClass().uncheckedCast()
            }

            data class ReportInfo(
                val name: String,
                val type: Class<*>,
                val implType: Class<*>?,
                val method: Method
            )

            val reportInfos = containerClass.methods.asSequence()
                .filter { !it.declaringClass.isAssignableFrom(ReportContainer::class.java) }
                .filter { it.parameterCount == 0 }
                .filter { Report::class.java.isAssignableFrom(it.returnType) }
                .filter { it.name.run { length >= 4 && startsWith("get") && get(3) == get(3).toUpperCase() } }
                .map {
                    ReportInfo(
                        name = it.name.substring(3).fromUpperCamelToLowerCamel(),
                        type = it.returnType,
                        implType = when (it.returnType) {
                            SingleFileReport::class.java -> TaskGeneratedSingleFileReport::class.java
                            else -> null
                        },
                        method = it
                    )
                }
                .distinctBy { it.name }
                .toList()

            val classNode = ClassNode().apply classNode@{
                version = V1_8
                access = ACC_PUBLIC
                name = getInternalName(containerClass) + "\$\$TaskReportContainer"
                superName = getInternalName(TaskReportContainer::class.java)
                interfaces = mutableListOf(getInternalName(containerClass))

                methods = mutableListOf()
                methods.add(MethodNode(ACC_PUBLIC, "", getMethodDescriptor(VOID_TYPE, getType(Task::class.java)), null, null).apply methodNode@{
                    visibleAnnotations = mutableListOf(AnnotationNode(getDescriptor(Inject::class.java)))
                    instructions = InsnList().apply instructions@{
                        add(LabelNode())

                        add(VarInsnNode(ALOAD, 0))
                        add(reportSuperType.toInsnNode())
                        add(VarInsnNode(ALOAD, 1))
                        val decoratorClass = TaskReportContainer::class.java.classLoader.tryLoadClass("org.gradle.api.internal.CollectionCallbackActionDecorator")
                        val decoratorCtor = decoratorClass?.let { TaskReportContainer::class.java.findConstructor(Class::class.java, Task::class.java, it) }
                        if (decoratorCtor != null) {
                            add(FieldInsnNode(GETSTATIC, getInternalName(decoratorClass), "NOOP", getDescriptor(decoratorClass)))
                            add(MethodInsnNode(INVOKESPECIAL, [email protected], [email protected], getConstructorDescriptor(decoratorCtor), false))
                        } else {
                            add(MethodInsnNode(INVOKESPECIAL, [email protected], [email protected], getMethodDescriptor(VOID_TYPE, getType(Class::class.java), getType(Task::class.java)), false))
                        }

                        reportInfos.forEach { info ->
                            if (info.implType == null) {
                                if (info.method.isDefault) {
                                    return@forEach
                                } else {
                                    throw IllegalStateException("Report implementation type can't be defined for ${info.method}")
                                }
                            }
                            add(VarInsnNode(ALOAD, 0))
                            add(info.implType.toInsnNode())
                            add(InsnNode(ICONST_2))
                            add(TypeInsnNode(ANEWARRAY, getInternalName(Any::class.java)))
                            add(InsnNode(DUP))
                            add(InsnNode(ICONST_0))
                            add(info.name.toInsnNode())
                            add(InsnNode(AASTORE))
                            add(InsnNode(DUP))
                            add(InsnNode(ICONST_1))
                            add(VarInsnNode(ALOAD, 1))
                            add(InsnNode(AASTORE))
                            add(MethodInsnNode(INVOKEVIRTUAL, [email protected], "add", getMethodDescriptor(getType(Report::class.java), getType(Class::class.java), getType(Array::class.java)), false))
                        }

                        add(InsnNode(RETURN))
                    }
                    maxStack = 1
                    maxLocals = 1
                })

                reportInfos.forEach { info ->
                    if (info.implType == null) return@forEach

                    methods.add(MethodNode(ACC_PUBLIC, info.method.name, getMethodDescriptor(info.method), null, null).apply methodNode@{
                        instructions = InsnList().apply instructions@{
                            add(LabelNode())
                            add(VarInsnNode(ALOAD, 0))
                            add(info.name.toInsnNode())
                            add(MethodInsnNode(INVOKEVIRTUAL, [email protected], "getByName", getMethodDescriptor(getType(Any::class.java), getType(String::class.java)), false))
                            add(TypeInsnNode(CHECKCAST, getInternalName(info.type)))
                            add(InsnNode(ARETURN))
                        }
                        maxStack = 1
                        maxLocals = 1
                    })
                }
            }

            val classWriter = ClassWriter(COMPUTE_MAXS)
            classNode.accept(classWriter)
            val bytecode = classWriter.toByteArray()
            ClassReader(bytecode).accept(CheckClassAdapter(ClassWriter(0)))

            return ClassLoader::class.java.getDeclaredMethod("defineClass", String::class.java, ByteArray::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
                .apply { isAccessible = true }
                .invoke(containerClass.classLoader, classNode.name.replace('/', '.'), bytecode, 0, bytecode.size)
                .uncheckedCast()
        }
    })

val > Class.taskReportContainerClass: Class get() = taskReportContainerClassCache[this.uncheckedCast()].uncheckedCast()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy