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.*
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 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.*
import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
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