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

name.remal.gradle_plugins.dsl.extensions.java.lang.Class.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
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.accept
import name.remal.buildMap
import name.remal.findMethod
import name.remal.isFinal
import name.remal.lambda.Function1
import name.remal.lambda.VoidFunction1
import name.remal.nullIf
import name.remal.toInsnNode
import name.remal.toLoadVarInsn
import name.remal.toReturnInsn
import name.remal.uncheckedCast
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.ACC_FINAL
import org.objectweb.asm.Opcodes.ACC_PRIVATE
import org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.objectweb.asm.Opcodes.ACC_SYNTHETIC
import org.objectweb.asm.Opcodes.ALOAD
import org.objectweb.asm.Opcodes.ARETURN
import org.objectweb.asm.Opcodes.ATHROW
import org.objectweb.asm.Opcodes.CHECKCAST
import org.objectweb.asm.Opcodes.DUP
import org.objectweb.asm.Opcodes.GETFIELD
import org.objectweb.asm.Opcodes.INVOKEINTERFACE
import org.objectweb.asm.Opcodes.INVOKESPECIAL
import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
import org.objectweb.asm.Opcodes.NEW
import org.objectweb.asm.Opcodes.PUTFIELD
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.getDescriptor
import org.objectweb.asm.Type.getInternalName
import org.objectweb.asm.Type.getMethodDescriptor
import org.objectweb.asm.Type.getType
import org.objectweb.asm.tree.AnnotationNode
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.TypeInsnNode
import org.objectweb.asm.tree.VarInsnNode
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)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy