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

com.itangcent.intellij.extend.guice.KotlinModule.kt Maven / Gradle / Ivy

Go to download

Help for developing plugins for JetBrains products. KotlinAnAction:provide ActionContext(support inject guice) for actionPerformed

The newest version!
package com.itangcent.intellij.extend.guice

import com.google.inject.AbstractModule
import com.google.inject.Key
import com.google.inject.ProvisionException
import com.google.inject.TypeLiteral
import com.google.inject.binder.AnnotatedBindingBuilder
import com.google.inject.binder.LinkedBindingBuilder
import com.google.inject.matcher.Matchers
import com.google.inject.name.Names
import com.google.inject.spi.InjectionListener
import com.google.inject.spi.TypeEncounter
import com.google.inject.spi.TypeListener
import com.itangcent.common.utils.collectDeclaredMethod
import com.itangcent.common.utils.privileged
import com.itangcent.intellij.context.ActionContext
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import kotlin.reflect.KClass
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaField

open class KotlinModule : AbstractModule() {

    /** @see Binder.bind
     */
    fun  bind(kClass: KClass): AnnotatedBindingBuilder {
        return this.bind(kClass.java)
    }

    /**
     * Binds a post injection hook method annotated with the given annotation to the given method
     * handler.
     */
    protected fun  bindMethodHandler(
        annotationType: Class,
        methodHandler: MethodHandler
    ) {
        bindMethodHandler(annotationType, EncounterProvider.encounterProvider(methodHandler))
    }

    private fun  bindMethodHandler(
        annotationType: Class,
        encounterProvider: EncounterProvider>
    ) {
        bindListener(Matchers.any(), object : TypeListener {
            override fun  hear(injectableType: TypeLiteral, encounter: TypeEncounter) {
                // #todo:provider暂时无法完成PostConstruct
                val type = injectableType.rawType
                collectDeclaredMethod(type) { method ->
                    if (Modifier.isStatic(method.modifiers)) {
                        return@collectDeclaredMethod
                    }
                    val annotation = method.getAnnotation(annotationType)
                    if (annotation != null) {
                        val provider = encounterProvider.get(encounter)

                        encounter.register(InjectionListener { injectee ->
                            val methodHandler = provider.get()
                            try {
                                methodHandler.afterInjection(injectee, annotation, method)
                            } catch (ie: InvocationTargetException) {
                                val e = ie.targetException
                                throw ProvisionException(e.message, e)
                            } catch (e: IllegalAccessException) {
                                throw ProvisionException(e.message, e)
                            }
                        })
                    }
                }
            }
        })
    }


    /**
     * Binds a post injection hook field annotated with the given annotation to the given field
     * handler.
     */
    protected fun  bindFieldHandler(
        annotationType: Class,
        fieldHandler: FieldHandler
    ) {
        bindFieldHandler(annotationType, EncounterProvider.encounterProvider(fieldHandler))
    }


    private fun  bindFieldHandler(
        annotationType: Class,
        encounterProvider: EncounterProvider>
    ) {
        bindListener(Matchers.any(), object : TypeListener {
            override fun  hear(injectableType: TypeLiteral, encounter: TypeEncounter) {
                // #todo:provider暂时无法完成PostConstruct
                val type = injectableType.rawType
                val fields = type.declaredFields
                val names: HashSet = HashSet()
                for (field in fields) {
                    val annotation = field.getAnnotation(annotationType)
                    if (annotation != null) {
                        names.add(field.name)
                        val provider = encounterProvider.get(encounter)

                        encounter.register(InjectionListener { injectee ->
                            val fieldHandler = provider.get()
                            try {
                                fieldHandler.afterInjection(injectee, annotation, field)
                            } catch (ie: InvocationTargetException) {
                                val e = ie.targetException
                                throw ProvisionException(e.message, e)
                            } catch (e: IllegalAccessException) {
                                throw ProvisionException(e.message, e)
                            }
                        })
                    }
                }
                val kotlinCls = annotationType.kotlin
                for (declaredMemberProperty in kotlinCls.declaredMemberProperties) {
                    val annotation = declaredMemberProperty.findAnnotation()
                    if (annotation != null && names.add(declaredMemberProperty.name)) {
                        val provider = encounterProvider[encounter]
                        declaredMemberProperty.javaField?.let {
                            encounter.register(InjectionListener { injectee ->
                                val fieldHandler = provider.get()

                                try {
                                    fieldHandler.afterInjection(
                                        injectee, annotation,
                                        it
                                    )
                                } catch (ie: InvocationTargetException) {
                                    val e = ie.targetException
                                    throw ProvisionException(e.message, e)
                                } catch (e: IllegalAccessException) {
                                    throw ProvisionException(e.message, e)
                                }
                            })
                        }
                    }
                }
            }
        })
    }

    /**
     * A helper method to bind the given type with the binding annotation.
     *
     * This allows you to replace this code ` bind(Key.get(MyType.class, SomeAnnotation.class))
    `*
     *
     * with this ` bind(KMyType.class, SomeAnnotation.class) `
     */
    protected fun  bind(
        type: KClass,
        annotationType: Class
    ): LinkedBindingBuilder {
        return bind(Key.get(type.java, annotationType))
    }

    /**
     * A helper method to bind the given type with the binding annotation.
     *
     * This allows you to replace this code ` bind(Key.get(MyType.class, SomeAnnotation.class))
    `*
     *
     * with this ` bind(KMyType.class, SomeAnnotation.class) `
     */
    protected fun  bind(
        type: Class,
        annotationType: Class
    ): LinkedBindingBuilder {
        return bind(Key.get(type, annotationType))
    }

    /**
     * A helper method to bind the given type with the binding annotation.
     *
     * This allows you to replace this code ` bind(Key.get(MyType.class, someAnnotation))
    ` *
     *
     * with this ` bind(KMyType.class, someAnnotation) `
     */
    protected fun  bind(type: Class, annotation: Annotation): LinkedBindingBuilder {
        return bind(Key.get(type, annotation))
    }

    /**
     * A helper method to bind the given type with the binding annotation.
     *
     * This allows you to replace this code ` bind(Key.get(MyType.class, someAnnotation))
    ` *
     *
     * with this ` bind(KMyType.class, someAnnotation) `
     */
    protected fun  bind(type: KClass, annotation: Annotation): LinkedBindingBuilder {
        return bind(Key.get(type.java, annotation))
    }

    /**
     * A helper method to bind the given type with the [com.google.inject.name.Named] annotation
     * of the given text value.
     *
     * This allows you to replace this code ` bind(Key.get(MyType.class, Names.named("myName")))
    ` *
     *
     * with this ` bind(KMyType.class, "myName") `
     */
    protected fun  bind(type: KClass, namedText: String): LinkedBindingBuilder {
        return bind(type, Names.named(namedText))
    }

    /**
     * A helper method to bind the given type with the [com.google.inject.name.Named] annotation
     * of the given text value.
     *
     * This allows you to replace this code ` bind(Key.get(MyType.class, Names.named("myName")))
    ` *
     *
     * with this ` bind(KMyType.class, "myName") `
     */
    protected fun  bind(type: Class, namedText: String): LinkedBindingBuilder {
        return bind(type, Names.named(namedText))
    }

    /**
     * A helper method which binds a named instance to a key defined by the given name and the
     * instances type. So this method is short hand for
     *
     * ` bind(instance.getClass(), name).toInstance(instance); `
     */
    @Suppress("UNCHECKED_CAST")
    protected fun  bindInstance(name: String, instance: T) {
        // TODO not sure the generics ninja to avoid this cast
        val aClass: KClass = instance::class as KClass
        bind(aClass, name).toInstance(instance)
    }

    /**
     * A helper method which binds a named instance to a key defined by the given name and the
     * instances type. So this method is short hand for
     *
     * ` bind(instance.getClass(), name).toInstance(instance); `
     */
    @Suppress("UNCHECKED_CAST")
    protected fun  bindInstance(instance: T) {
        // TODO not sure the generics ninja to avoid this cast
        val aClass: KClass = instance::class as KClass
        bind(aClass).toInstance(instance)
    }

    @Suppress("UNCHECKED_CAST")
    protected fun  bindInstance(cls: KClass, instance: T) {
        bind(cls).toInstance(instance)
    }

    @Suppress("UNCHECKED_CAST")
    protected fun  bindInstance(cls: Class, instance: T) {
        bind(cls).toInstance(instance)
    }

    override fun configure() {

        bindFieldHandler(PostConstruct::class.java, object : FieldHandler {
            @Throws(InvocationTargetException::class, IllegalAccessException::class)
            override fun afterInjection(injectee: Any?, annotation: Annotation, field: Field) {
                val context = ActionContext.getContext()
                if (context != null) {
                    field.privileged {
                        var fieldValue: Any? = field.get(injectee)
                        if (fieldValue == null) {
                            fieldValue = context.instance(field.type.kotlin)
                            field.set(injectee, fieldValue)
                        } else {
                            context.init(fieldValue)
                        }
                    }
                }
            }
        })

        bindMethodHandler(PostConstruct::class.java, object : MethodHandler {
            @Throws(InvocationTargetException::class, IllegalAccessException::class)
            override fun afterInjection(injectee: Any?, annotation: Annotation, method: Method) {
                method.privileged { it.invoke(injectee) }
            }
        })

    }
}