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

com.fasterxml.jackson.module.kotlin.KotlinModule.kt Maven / Gradle / Ivy

package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.databind.introspect.*
import com.fasterxml.jackson.databind.module.SimpleModule
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.util.HashSet
import kotlin.reflect.*
import kotlin.reflect.jvm.kotlinFunction

private val metadataFqName = "kotlin.Metadata"

fun Class<*>.isKotlinClass(): Boolean {
    return this.declaredAnnotations.singleOrNull { it.annotationClass.java.name == metadataFqName } != null
}

class KotlinModule() : SimpleModule(PackageVersion.VERSION) {
    companion object {
        private val serialVersionUID = 1L
    }

    val requireJsonCreatorAnnotation: Boolean = false

    val impliedClasses = HashSet>(setOf(
            Pair::class.java,
            Triple::class.java
    ))

    override fun setupModule(context: SetupContext) {
        super.setupModule(context)

        context.addValueInstantiators(KotlinInstantiators())

        fun addMixin(clazz: Class<*>, mixin: Class<*>) {
            impliedClasses.add(clazz)
            context.setMixInAnnotations(clazz, mixin)
        }

        context.appendAnnotationIntrospector(KotlinNamesAnnotationIntrospector(this))

        // ranges
        addMixin(IntRange::class.java, ClosedRangeMixin::class.java)
        addMixin(CharRange::class.java, ClosedRangeMixin::class.java)
        addMixin(LongRange::class.java, ClosedRangeMixin::class.java)
    }
}


internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule) : NopAnnotationIntrospector() {
    /*
    override public fun findNameForDeserialization(annotated: Annotated?): PropertyName? {
        // This should not do introspection here, only for explicit naming by annotations
        return null
    }
    */

    // since 2.4
    override fun findImplicitPropertyName(member: AnnotatedMember): String? {
        if (member is AnnotatedParameter) {
            return findKotlinParameterName(member)
        }
        return null
    }

    @Suppress("UNCHECKED_CAST")
    override fun hasCreatorAnnotation(member: Annotated): Boolean {
        // don't add a JsonCreator to any constructor if one is declared already

        if (member is AnnotatedConstructor && !member.declaringClass.isEnum) {
            // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
            if (member.getParameterCount() > 0 && member.getDeclaringClass().isKotlinClass()) {
                val kClass = (member.getDeclaringClass() as Class).kotlin
                val kConstructor = (member.getAnnotated() as Constructor).kotlinFunction

                if (kConstructor != null) {
                    val isPrimaryConstructor = kClass.primaryConstructor == kConstructor ||
                            (kClass.primaryConstructor == null && kClass.constructors.size == 1)
                    val anyConstructorHasJsonCreator = kClass.constructors.any { it.annotations.any { it.annotationClass.java == JsonCreator::class.java } } // member.getDeclaringClass().getConstructors().any { it.getAnnotation() != null }

                    val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions?.any {
                        it.annotations.any { it.annotationClass.java == JvmStatic::class.java } &&
                                it.annotations.any { it.annotationClass.java == JsonCreator::class.java }
                    } ?: false
                    val anyStaticMethodIsJsonCreator = member.type.rawClass.declaredMethods.any {
                        val isStatic = Modifier.isStatic(it.modifiers)
                        val isCreator = it.declaredAnnotations.any { it.annotationClass.java == JsonCreator::class.java }
                        isStatic && isCreator
                    }

                    // TODO:  should we do this check or not?  It could cause failures if we miss another way a property could be set
                    // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet()
                    // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) }

                    val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null }

                    val isSingleStringConstructor = kConstructor.parameters.size == 1 &&
                                                    kConstructor.parameters[0].type == String::class.defaultType &&
                                                    kClass.declaredMemberProperties.none {
                                                        it.name == kConstructor.parameters[0].name && it.returnType == kConstructor.parameters[0].type
                                                    }
                    val implyCreatorAnnotation = isPrimaryConstructor
                            && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator || anyStaticMethodIsJsonCreator)
                            && areAllParametersValid
                            && !isSingleStringConstructor

                    return implyCreatorAnnotation
                }
            }
        }
        return false
    }

    @Suppress("UNCHECKED_CAST")
    protected fun findKotlinParameterName(param: AnnotatedParameter): String? {
        if (param.getDeclaringClass().isKotlinClass()) {
            val member = param.getOwner().getMember()
            val name = if (member is Constructor<*>) {
                val ctor = (member as Constructor)
                val ctorParmCount = ctor.parameterTypes.size
                val ktorParmCount = ctor.kotlinFunction?.parameters?.size ?: 0
                if (ktorParmCount > 0 && ktorParmCount == ctorParmCount) {
                    ctor.kotlinFunction?.parameters?.get(param.index)?.name
                } else {
                    null
                }
            } else if (member is Method) {
                try {
                    val temp = member.kotlinFunction

                    val firstParamKind = temp?.parameters?.firstOrNull()?.kind
                    val idx = if (firstParamKind != KParameter.Kind.VALUE) param.index + 1 else param.index
                    val parmCount = temp?.parameters?.size ?: 0
                    if (parmCount > idx) {
                        temp?.parameters?.get(idx)?.name
                    }
                    else {
                        null
                    }
                }
                catch (ex: KotlinReflectionInternalError) {
                    null
                }
            } else {
                null
            }
            return name
        }
        return null
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy