name.remal.gradle_plugins.dsl.extensions.java.lang.Class.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.extensions
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import groovy.lang.Closure
import groovy.lang.MetaClass
import name.remal.*
import name.remal.lambda.Function1
import name.remal.lambda.VoidFunction1
import org.codehaus.groovy.runtime.DefaultGroovyMethods
import org.gradle.api.Action
import org.gradle.api.internal.GeneratedSubclass
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.io.InputStream
import java.lang.reflect.Method
import java.util.function.Consumer
import javax.inject.Inject
import kotlin.ByteArray
import kotlin.Int
import kotlin.String
import java.util.function.Function as JFunction
import kotlin.Function1 as KFunction1
import kotlin.reflect.KFunction1 as KReflectFunction1
val Class<*>.groovyMetaClass: MetaClass get() = DefaultGroovyMethods.getMetaClass(this)
val Class<*>.canBeConfigurer: Boolean
get() = Action::class.java.isAssignableFrom(this)
|| Closure::class.java.isAssignableFrom(this)
|| Consumer::class.java.isAssignableFrom(this)
|| JFunction::class.java.isAssignableFrom(this)
|| KFunction1::class.java.isAssignableFrom(this)
|| KReflectFunction1::class.java.isAssignableFrom(this)
|| Function1::class.java.isAssignableFrom(this)
|| VoidFunction1::class.java.isAssignableFrom(this)
fun Class<*>.getRequiredResource(resourceName: String) = unwrapGradleGenerated().let { clazz ->
clazz.getResource(resourceName)
?: throw IllegalStateException("$clazz: classpath resource can't be found: $resourceName")
}
fun Class<*>.getRequiredResourceAsStream(resourceName: String): InputStream = getRequiredResource(resourceName)
.openConnection()
.apply { useCaches = false }
.getInputStream()
fun Class.unwrapGradleGenerated(): Class {
var clazz: Class<*> = this
while (GeneratedSubclass::class.java.isAssignableFrom(clazz)) {
clazz = clazz.superclass
}
return clazz.uncheckedCast()
}
private val delegateClassOfCache: LoadingCache, Class<*>>, Class<*>> = CacheBuilder.newBuilder()
.weakValues()
.build(object : CacheLoader, Class<*>>, Class<*>>() {
@Suppress("LongMethod")
override fun load(key: Pair, Class<*>>): Class<*> {
val (currentClass, delegateClass) = key
if (!currentClass.isInterface) throw IllegalArgumentException("$currentClass is not an interface")
if (currentClass.isFinal) throw IllegalArgumentException("$currentClass is final class")
val methodsMapping = run {
val processedMethods = hashSetOf()
buildMap {
currentClass.methods.forEach { method ->
if (!processedMethods.add(method.name + getMethodDescriptor(method))) return@forEach
if (method.declaringClass == Any::class.java) return@forEach
if (method.name == "toString" && method.parameterCount == 0) return@forEach
val delegateMethod = delegateClass.findCompatibleMethod(method.returnType, method.name, *method.parameterTypes)
put(method, delegateMethod)
}
}
}
val classNode = ClassNode().apply classNode@{
version = V1_8
access = ACC_PUBLIC
name = getInternalName(currentClass) + "\$\$Delegate\$\$" + delegateClass.name.replace('.', '_')
superName = getInternalName(Any::class.java)
interfaces = mutableListOf(getInternalName(currentClass))
fields = mutableListOf()
val delegateFieldName = "\$\$delegate"
val delegateFieldDescriptor = getDescriptor(delegateClass)
fields.add(FieldNode(ACC_PRIVATE or ACC_FINAL or ACC_SYNTHETIC, delegateFieldName, delegateFieldDescriptor, null, null))
methods = mutableListOf()
methods.add(MethodNode(ACC_PUBLIC, "", getMethodDescriptor(VOID_TYPE, getType(delegateClass)), null, null).apply methodNode@{
visibleAnnotations = mutableListOf(AnnotationNode(getDescriptor(Inject::class.java)))
instructions = InsnList().apply instructions@{
add(LabelNode())
add(VarInsnNode(ALOAD, 0))
add(MethodInsnNode(INVOKESPECIAL, [email protected], [email protected], "()V", false))
add(VarInsnNode(ALOAD, 0))
add(VarInsnNode(ALOAD, 1))
add(FieldInsnNode(PUTFIELD, [email protected], delegateFieldName, delegateFieldDescriptor))
add(InsnNode(RETURN))
}
maxStack = 1
maxLocals = 1
})
methodsMapping.forEach { method, delegateMethod ->
if (delegateMethod == null && method.isDefault) return@forEach
methods.add(MethodNode(ACC_PUBLIC, method.name, getMethodDescriptor(method), null, null).apply methodNode@{
instructions = InsnList().apply instructions@{
add(LabelNode())
if (delegateMethod != null) {
add(VarInsnNode(ALOAD, 0))
add(FieldInsnNode(GETFIELD, [email protected], delegateFieldName, delegateFieldDescriptor))
method.parameterTypes.forEachIndexed { index, type ->
add(getType(type).toLoadVarInsn(index + 1))
val delegateType = delegateMethod.parameterTypes[index]
if (delegateType != type) {
add(TypeInsnNode(CHECKCAST, getInternalName(delegateType)))
}
}
if (delegateClass.isInterface) {
add(MethodInsnNode(INVOKEINTERFACE, getInternalName(delegateClass), delegateMethod.name, getMethodDescriptor(delegateMethod), true))
} else {
add(MethodInsnNode(INVOKEVIRTUAL, getInternalName(delegateClass), delegateMethod.name, getMethodDescriptor(delegateMethod), false))
}
if (method.returnType != delegateMethod.returnType) {
add(TypeInsnNode(CHECKCAST, getInternalName(method.returnType)))
}
add(getType(method.returnType).toReturnInsn())
} else {
add(TypeInsnNode(NEW, getInternalName(UnsupportedOperationException::class.java)))
add(InsnNode(DUP))
add("$delegateClass doesn't have any compatible method with $method".toInsnNode())
add(MethodInsnNode(INVOKESPECIAL, getInternalName(UnsupportedOperationException::class.java), "", getMethodDescriptor(VOID_TYPE, getType(String::class.java)), false))
add(InsnNode(ATHROW))
}
}
maxStack = 1
maxLocals = 1
})
}
methods.add(MethodNode(ACC_PUBLIC, "toString", getMethodDescriptor(getType(String::class.java)), null, null).apply methodNode@{
instructions = InsnList().apply instructions@{
add(LabelNode())
add(VarInsnNode(ALOAD, 0))
add(FieldInsnNode(GETFIELD, [email protected], delegateFieldName, delegateFieldDescriptor))
if (delegateClass.isInterface) {
add(MethodInsnNode(INVOKEINTERFACE, getInternalName(delegateClass), [email protected], [email protected], true))
} else {
add(MethodInsnNode(INVOKEVIRTUAL, getInternalName(delegateClass), [email protected], [email protected], false))
}
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(currentClass.classLoader, classNode.name.replace('/', '.'), bytecode, 0, bytecode.size)
.uncheckedCast()
}
})
fun Class.getDelegateClassOf(other: Class<*>): Class {
return delegateClassOfCache[this to other].uncheckedCast()
}
fun Class.newDelegateOf(obj: Any): T {
return getDelegateClassOf(obj.javaClass).getConstructor(obj.javaClass).newInstance(obj)
}
fun Class<*>.findCompatibleMethod(name: String, vararg paramTypes: Class<*>): Method? {
if (paramTypes.isEmpty()) {
return findMethod(name)
}
val candidateMethods = methods.filter { method -> method.name == name && method.parameterCount == paramTypes.size }
return candidateMethods.firstOrNull { method -> paramTypes.allIndexed { index, paramType -> method.parameterTypes[index] == paramType } }
?: candidateMethods.firstOrNull { method -> paramTypes.allIndexed { index, paramType -> method.parameterTypes[index].isAssignableFrom(paramType) } }
}
fun Class<*>.findCompatibleMethod(returnType: Class<*>, name: String, vararg paramTypes: Class<*>): Method? {
if (paramTypes.isEmpty()) {
return findMethod(name).nullIf { !returnType.isAssignableFrom([email protected]) }
}
val candidateMethods = methods.filter { method -> method.name == name && method.parameterCount == paramTypes.size && returnType.isAssignableFrom(method.returnType) }
return candidateMethods.firstOrNull { method -> paramTypes.allIndexed { index, paramType -> method.parameterTypes[index] == paramType } }
?: candidateMethods.firstOrNull { method -> paramTypes.allIndexed { index, paramType -> method.parameterTypes[index].isAssignableFrom(paramType) } }
}
private inline fun Array.allIndexed(predicate: (index: Int, element: T) -> Boolean): Boolean {
forEachIndexed { index, element ->
if (!predicate(index, element)) {
return false
}
}
return true
}
fun Class<*>.getCompatibleMethod(name: String, vararg paramTypes: Class<*>): Method {
val method = findCompatibleMethod(name, *paramTypes)
if (method != null) {
return method
}
throw CompatibleMethodNotFoundException(buildString {
append("Compatible method not found: ")
append([email protected])
append('.')
append(name)
append('(')
paramTypes.joinTo(this, ", ", transform = { it.name })
append(')')
})
}
fun Class<*>.getCompatibleMethod(returnType: Class<*>, name: String, vararg paramTypes: Class<*>): Method {
val method = findCompatibleMethod(returnType, name, *paramTypes)
if (method != null) {
return method
}
throw CompatibleMethodNotFoundException(buildString {
append("Compatible method not found: ")
append(returnType.name)
append(' ')
append([email protected])
append('.')
append(name)
append('(')
paramTypes.joinTo(this, ", ", transform = { it.name })
append(')')
})
}
class CompatibleMethodNotFoundException : RuntimeException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}