name.remal.gradle_plugins.dsl.utils.wrapWithInjectedConstructorParams.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-plugins-kotlin-dsl Show documentation
Show all versions of gradle-plugins-kotlin-dsl Show documentation
Remal Gradle plugins: gradle-plugins-kotlin-dsl
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
}