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

name.remal.gradle_plugins.dsl.utils.wrapWithInjectedConstructorParams.kt Maven / Gradle / Ivy

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

import name.remal.accept
import name.remal.gradle_plugins.dsl.reflective_project_plugin.action_param_injector.ActionParamInjector
import name.remal.gradle_plugins.dsl.reflective_project_plugin.action_param_injector.actionParamInjectors
import name.remal.isFinal
import name.remal.isPrivate
import name.remal.nullIfEmpty
import name.remal.uncheckedCast
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Opcodes.ACC_PRIVATE
import org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.objectweb.asm.Opcodes.ACC_STATIC
import org.objectweb.asm.Opcodes.ACC_SYNTHETIC
import org.objectweb.asm.Opcodes.ALOAD
import org.objectweb.asm.Opcodes.GETSTATIC
import org.objectweb.asm.Opcodes.INVOKESPECIAL
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.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldNode
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.VarInsnNode
import org.objectweb.asm.util.CheckClassAdapter
import java.util.concurrent.atomic.AtomicLong
import javax.inject.Inject

private val wrapWithInjectedConstructorParamsCounter = AtomicLong(0)

@Suppress("ComplexMethod")
fun  wrapWithInjectedConstructorParams(originalClass: Class, project: Project): Class {
    val constructors = originalClass.declaredConstructors.filter { !it.isPrivate }
    if (constructors.size == 1) {
        if (constructors.single().parameterCount == 0) {
            return originalClass
        }
    }

    if (originalClass.isFinal) {
        throw IllegalArgumentException("$originalClass can't be extended as it's a final class")
    }

    val candidateConstructors = constructors.filter { it.isAnnotationPresent(Inject::class.java) }.nullIfEmpty() ?: constructors
    if (candidateConstructors.isEmpty()) {
        throw IllegalArgumentException("$originalClass can't be extended as there are no constructors to inject params")
    } else if (candidateConstructors.size >= 2) {
        throw IllegalArgumentException("$originalClass can't be extended as there are several constructors to inject params")
    }

    val constructor = candidateConstructors.single()
    val params = mutableMapOf, Any?>().apply {
        constructor.parameterTypes.forEach {
            computeIfAbsent(it) { type ->
                val injector = actionParamInjectors.firstOrNull { type.isAssignableFrom(it.paramType) }
                    ?: throw IllegalStateException("${ActionParamInjector::class.java.simpleName} can't be found for $type")
                return@computeIfAbsent injector.createValue(project)
            }
        }
    }

    val fieldNames = params.mapValues { "$$" + it.key.name.replace('.', '_') }

    val classNode = ClassNode().apply classNode@{
        version = V1_8
        access = ACC_PUBLIC
        name = getInternalName(originalClass) + "\$\$WrappedWithInjectedConstructorParams" + wrapWithInjectedConstructorParamsCounter.incrementAndGet()
        superName = getInternalName(originalClass)

        fields = mutableListOf()
        fieldNames.forEach { type, name ->
            fields.add(FieldNode(ACC_PRIVATE or ACC_STATIC or ACC_SYNTHETIC, name, getDescriptor(type), null, null))
        }

        methods = mutableListOf()
        methods.add(MethodNode(ACC_PUBLIC, "", getMethodDescriptor(VOID_TYPE), null, null).apply methodNode@{
            instructions = InsnList().apply instructions@{
                add(LabelNode())
                add(VarInsnNode(ALOAD, 0))

                constructor.parameterTypes.forEach { type ->
                    add(FieldInsnNode(GETSTATIC, [email protected], fieldNames[type], getDescriptor(type)))
                }
                add(MethodInsnNode(INVOKESPECIAL, [email protected], [email protected], getConstructorDescriptor(constructor), false))

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

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

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

    params.forEach { type, value ->
        wrappedClass
            .getDeclaredField(fieldNames[type])
            .apply { isAccessible = true }
            .set(null, value)
    }

    return wrappedClass
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy