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

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.*
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.*
import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
import org.objectweb.asm.util.CheckClassAdapter
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import javax.inject.Inject

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