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.*
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)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy