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

com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospectorModule.scala Maven / Gradle / Ivy

The newest version!
package com.fasterxml.jackson.module.scala.introspect

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.databind.`type`.{CollectionLikeType, MapLikeType, ReferenceType, SimpleType}
import com.fasterxml.jackson.databind.cfg.MapperConfig
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
import com.fasterxml.jackson.databind.deser._
import com.fasterxml.jackson.databind.introspect._
import com.fasterxml.jackson.databind.util.{AccessPattern, LookupCache}
import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JavaType, MapperFeature}
import com.fasterxml.jackson.module.scala.{DefaultLookupCacheFactory, JacksonModule, LookupCacheFactory}
import com.fasterxml.jackson.module.scala.util.Implicits._

import java.lang.annotation.Annotation
import scala.collection.mutable.{Map => MutableMap}

object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueInstantiators {

  def propertyFor(a: Annotated): Option[PropertyDescriptor] = {
    a match {
      case ap: AnnotatedParameter =>
        _descriptorFor(ap.getDeclaringClass).flatMap { d =>
          d.properties.find { p =>
            p.param.exists { cp =>
              (cp.constructor == ap.getOwner.getAnnotated) && (cp.index == ap.getIndex)
            }
          }
        }
      case am: AnnotatedMember =>
        _descriptorFor(am.getDeclaringClass).flatMap { d =>
          d.properties.find { p =>
            (p.field ++ p.getter ++ p.setter ++ p.param ++ p.beanGetter ++ p.beanSetter).exists(_ == a.getAnnotated)
          }
        }
    }
  }

  override def findImplicitPropertyName(member: AnnotatedMember): String = {
    member match {
      case af: AnnotatedField => fieldName(af).orNull
      case am: AnnotatedMethod => methodName(am).orNull
      case ap: AnnotatedParameter => paramName(ap).orNull
      case _ => None.orNull
    }
  }

  override def hasIgnoreMarker(m: AnnotatedMember): Boolean = {
    val name = m.getName
    //special cases to prevent shadow fields associated with lazy vals being serialized
    name == "0bitmap$1" || name.endsWith("$lzy1") || name.contains("$default$") || super.hasIgnoreMarker(m)
  }

  override def hasCreatorAnnotation(a: Annotated): Boolean = {
    val jsonCreators: PartialFunction[Annotation, JsonCreator] = { case jc: JsonCreator => jc }

    a match {
      case ac: AnnotatedConstructor if (!isScala(ac)) => false
      case ac: AnnotatedConstructor =>
        val annotatedFound = _descriptorFor(ac.getDeclaringClass).map { d =>
          d.properties
            .flatMap(_.param)
            .exists(_.constructor == ac.getAnnotated)
        }.getOrElse(false)

        // Ignore this annotation if there is another annotation that is actually annotated with @JsonCreator.
        val annotatedConstructor = {
          for (constructor <- ac.getDeclaringClass.getDeclaredConstructors;
               annotation: JsonCreator <- constructor.getAnnotations.collect(jsonCreators) if annotation.mode() != JsonCreator.Mode.DISABLED) yield constructor
        }.headOption

        // Ignore this annotation if it is Mode.DISABLED.
        val isDisabled = ac.getAnnotated.getAnnotations.collect(jsonCreators).exists(_.mode() == JsonCreator.Mode.DISABLED)

        annotatedFound && annotatedConstructor.forall(_ == ac.getAnnotated) && !isDisabled
      case _ => false
    }
  }

  override def findCreatorAnnotation(config: MapperConfig[_], a: Annotated): JsonCreator.Mode = {
    if (hasCreatorAnnotation(a)) {
      Option(findCreatorBinding(a)) match {
        case Some(mode) => mode
        case _ => JsonCreator.Mode.DEFAULT
      }
    } else None.orNull
  }

  override def findCreatorBinding(a: Annotated): JsonCreator.Mode = {
    Option(_findAnnotation(a, classOf[JsonCreator])) match {
      case Some(ann) => ann.mode()
      case _ => {
        if (isScala(a) && hasCreatorAnnotation(a)) {
          JsonCreator.Mode.PROPERTIES
        } else None.orNull
      }
    }
  }

  override def version(): Version = JacksonModule.version

  private class ScalaValueInstantiator(scalaAnnotationIntrospectorModule: ScalaAnnotationIntrospectorModule,
                                       delegate: StdValueInstantiator, config: DeserializationConfig, descriptor: BeanDescriptor)
    extends StdValueInstantiator(delegate) {

    private val overriddenConstructorArguments: Array[SettableBeanProperty] = {
      val overrides = scalaAnnotationIntrospectorModule.overrideMap.get(descriptor.beanType.getName)
        .map(_.overrides.toMap).getOrElse(Map.empty)
      val applyDefaultValues = config.isEnabled(MapperFeature.APPLY_DEFAULT_VALUES)
      val args = delegate.getFromObjectArguments(config)
      Option(args) match {
        case Some(array) if (applyDefaultValues || overrides.nonEmpty) => {
          array.map {
            case creator: CreatorProperty => {
              // Locate the constructor param that matches it
              descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match {
                case Some(pd) => {
                  if (applyDefaultValues) {
                    pd match {
                      case PropertyDescriptor(_, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _) => {
                        val updatedCreator = creator.withNullProvider(new NullValueProvider {
                          override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue()
                          override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC
                        })
                        updatedCreator match {
                          case cp: CreatorProperty => applyOverrides(cp, pd.name, overrides)
                          case cp => cp
                        }
                      }
                      case _ => applyOverrides(creator, pd.name, overrides)
                    }
                  } else {
                    applyOverrides(creator, pd.name, overrides)
                  }
                }
                case _ => creator
              }
            }
          }
        }
        case Some(array) => array
        case _ => Array.empty
      }
    }

    override def getFromObjectArguments(config: DeserializationConfig): Array[SettableBeanProperty] = {
      overriddenConstructorArguments
    }
  }

  private def applyOverrides(creator: CreatorProperty, propertyName: String,
                             overrides: Map[String, ClassHolder]): CreatorProperty = {
    overrides.get(propertyName) match {
      case Some(refHolder) => WrappedCreatorProperty(creator, refHolder)
      case _ => creator
    }
  }

  override def findValueInstantiator(config: DeserializationConfig, beanDesc: BeanDescription,
                                     defaultInstantiator: ValueInstantiator): ValueInstantiator = {

    if (isMaybeScalaBeanType(beanDesc.getBeanClass)) {

      _descriptorFor(beanDesc.getBeanClass).map { descriptor =>
        if (ScalaAnnotationIntrospectorModule.overrideMap.contains(beanDesc.getBeanClass.getName)
          || descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) {

          defaultInstantiator match {
            case std: StdValueInstantiator =>
              new ScalaValueInstantiator(ScalaAnnotationIntrospectorModule, std, config, descriptor)
            case other =>
              throw new IllegalArgumentException("Cannot customise a non StdValueInstantiator: " + other.getClass)
          }
        } else defaultInstantiator
      }.getOrElse(defaultInstantiator)

    } else defaultInstantiator
  }

  private def _descriptorFor(clz: Class[_]): Option[BeanDescriptor] = {
    val key = clz.getName
    val isScala = {
      Option(ScalaAnnotationIntrospectorModule._scalaTypeCache.get(key)) match {
        case Some(result) => result
        case _ => {
          val result = clz.extendsScalaClass(ScalaAnnotationIntrospectorModule.shouldSupportScala3Classes()) || clz.hasSignature
          ScalaAnnotationIntrospectorModule._scalaTypeCache.put(key, result)
          result
        }
      }
    }
    if (isScala) {
      Option(ScalaAnnotationIntrospectorModule._descriptorCache.get(key)) match {
        case Some(result) => Some(result)
        case _ => {
          val introspector = BeanIntrospector(clz)
          ScalaAnnotationIntrospectorModule._descriptorCache.put(key, introspector)
          Some(introspector)
        }
      }
    } else {
      None
    }
  }

  private def fieldName(af: AnnotatedField): Option[String] = {
    _descriptorFor(af.getDeclaringClass).flatMap { d =>
      d.properties.find(p => p.field.exists(_ == af.getAnnotated)).map(_.name)
    }
  }

  private def methodName(am: AnnotatedMethod): Option[String] = {
    _descriptorFor(am.getDeclaringClass).flatMap { d =>
      val getterSetter = d.properties.find(p => (p.getter ++ p.setter).exists(_ == am.getAnnotated)).map(_.name)
      getterSetter match {
        case Some(s) => Some(s)
        case _ => d.properties.find(p => p.name == am.getName).map(_.name)
      }
    }
  }

  private def paramName(ap: AnnotatedParameter): Option[String] = {
    _descriptorFor(ap.getDeclaringClass).flatMap { d =>
      d.properties.find(p => p.param.exists { cp =>
        cp.constructor == ap.getOwner.getAnnotated && cp.index == ap.getIndex
      }).map(_.name)
    }
  }

  private def isScalaPackage(pkg: Option[Package]): Boolean =
    pkg.exists(_.getName.startsWith("scala."))

  private[introspect] def isMaybeScalaBeanType(cls: Class[_]): Boolean = {
    val key = cls.getName
    val flag = Option(ScalaAnnotationIntrospectorModule._scalaTypeCache.get(key)) match {
      case Some(flag) => flag
      case _ => {
        val flag = cls.extendsScalaClass(ScalaAnnotationIntrospectorModule.shouldSupportScala3Classes()) || cls.hasSignature
        ScalaAnnotationIntrospectorModule._scalaTypeCache.put(key, flag)
        flag
      }
    }
    flag && !isScalaPackage(Option(cls.getPackage))
  }

  private def isScala(a: Annotated): Boolean = {
    a match {
      case ac: AnnotatedClass => isMaybeScalaBeanType(ac.getAnnotated)
      case am: AnnotatedMember => isMaybeScalaBeanType(am.getDeclaringClass)
    }
  }
}

trait ScalaAnnotationIntrospectorModule extends JacksonModule {
  this += { _.appendAnnotationIntrospector(JavaAnnotationIntrospector) }
  this += { _.appendAnnotationIntrospector(ScalaAnnotationIntrospector) }
  this += { _.addValueInstantiators(ScalaAnnotationIntrospector) }

  private var _lookupCacheFactory: LookupCacheFactory = DefaultLookupCacheFactory
  private var _shouldSupportScala3Classes: Boolean = true
  private var _descriptorCacheSize: Int = 100
  private var _scalaTypeCacheSize: Int = 1000

  private[introspect] var _descriptorCache: LookupCache[String, BeanDescriptor] =
    _lookupCacheFactory.createLookupCache(16, _descriptorCacheSize)

  private[introspect] var _scalaTypeCache: LookupCache[String, Boolean] =
    _lookupCacheFactory.createLookupCache(16, _scalaTypeCacheSize)

  private[introspect] val overrideMap = MutableMap[String, ClassOverrides]()

  /**
   * Replaces the [[LookupCacheFactory]]. The default factory uses [[com.fasterxml.jackson.databind.util.LRUMap]].
   * 

* Note that this clears the existing cache entries. It is best to set this up before you start using * the Jackson Scala Module for serializing/deserializing. *

* * @param lookupCacheFactory new factory * @see [[setDescriptorCacheSize]] * @see [[setScalaTypeCacheSize]] * @since 2.14.3 */ def setLookupCacheFactory(lookupCacheFactory: LookupCacheFactory): Unit = { _lookupCacheFactory = lookupCacheFactory recreateDescriptorCache() recreateScalaTypeCache() } /** * Resize the descriptorCache. The default size is 100. *

* Note that this clears the existing cache entries. It is best to set this up before you start using * the Jackson Scala Module for serializing/deserializing. *

* * @param size new size for the cache * @see [[setScalaTypeCacheSize]] * @see [[setLookupCacheFactory]] * @since 2.14.3 */ def setDescriptorCacheSize(size: Int): Unit = { _descriptorCacheSize = size recreateDescriptorCache() } /** * Resize the scalaTypeCache. The default size is 1000. *

* Note that this clears the existing cache entries. It is best to set this up before you start using * the Jackson Scala Module for serializing/deserializing. *

* * @param size new size for the cache * @see [[setDescriptorCacheSize]] * @see [[setLookupCacheFactory]] * @since 2.14.3 */ def setScalaTypeCacheSize(size: Int): Unit = { _scalaTypeCacheSize = size recreateScalaTypeCache() } private def recreateDescriptorCache(): Unit = { _descriptorCache.clear() _descriptorCache = _lookupCacheFactory.createLookupCache(16, _descriptorCacheSize) } private def recreateScalaTypeCache(): Unit = { _scalaTypeCache.clear() _scalaTypeCache = _lookupCacheFactory.createLookupCache(16, _scalaTypeCacheSize) } /** * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping * Scala primitives (eg Int, Long, Boolean). *

* This function is experimental and may be removed or significantly reworked in a later release. *

* These issues can be worked around by adding Jackson annotations on the affected fields. * This function is designed to be used when it is not possible to apply Jackson annotations. * * @param clazz the (case) class * @param fieldName the field name in the (case) class * @param referencedType the referenced type of the field - for `Option[Long]` - the referenced type is `Long` * @see [[getRegisteredReferencedValueType]] * @see [[clearRegisteredReferencedTypes()]] * @see [[clearRegisteredReferencedTypes(Class[_])]] * @since 2.13.1 */ def registerReferencedValueType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { val overrides = overrideMap.getOrElseUpdate(clazz.getName, ClassOverrides()).overrides overrides.get(fieldName) match { case Some(holder) => overrides.put(fieldName, holder.copy(valueClass = Some(referencedType))) case _ => overrides.put(fieldName, ClassHolder(valueClass = Some(referencedType))) } } /** * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping * Scala primitives (eg Int, Long, Boolean). *

* This function is experimental and may be removed or significantly reworked in a later release. *

* These issues can be worked around by adding Jackson annotations on the affected fields. * This function is designed to be used when it is not possible to apply Jackson annotations. * * @param clazz the (case) class * @param fieldName the field name in the (case) class * @return the referenced type of the field - for `Option[Long]` - the referenced type is `Long` * @see [[registerReferencedValueType]] * @since 2.13.1 */ def getRegisteredReferencedValueType(clazz: Class[_], fieldName: String): Option[Class[_]] = { overrideMap.get(clazz.getName).flatMap { overrides => overrides.overrides.get(fieldName).flatMap(_.valueClass) } } /** * clears the state associated with reference types for the given class * * @param clazz the class for which to remove the registered reference types * @see [[registerReferencedValueType]] * @see [[clearRegisteredReferencedTypes()]] * @since 2.13.1 */ def clearRegisteredReferencedTypes(clazz: Class[_]): Unit = { overrideMap.remove(clazz.getName) } /** * Clears all the state associated with reference types * * @see [[registerReferencedValueType]] * @see [[clearRegisteredReferencedTypes(Class[_])]] * @since 2.13.1 */ def clearRegisteredReferencedTypes(): Unit = { overrideMap.clear() } /** * Replace the descriptorCachescalaTypeCache. * * @param cache new cache instance * @return existing cache instance * @since 2.14.0 * @see [[setScalaTypeCacheSize]] * @see [[setLookupCacheFactory]] * @deprecated key type will change to String in v2.15.0 and this function will be removed in a later release */ @deprecated("key type will change to String in v2.15.0 and this function will be removed in a later release", "2.14.3") def setScalaTypeCache(cache: LookupCache[String, Boolean]): LookupCache[String, Boolean] = { val existingCache = _scalaTypeCache _scalaTypeCache = cache existingCache } /** * Sets whether we check for Scala3 classes (default is true). Setting this to false can improve performance. * * @param support whether we check for Scala3 classes * @since 2.14.0 */ def supportScala3Classes(support: Boolean): Unit = { _shouldSupportScala3Classes = support } /** * Gets whether we check for Scala3 classes (default is true). * * @return whether we check for Scala3 classes * @since 2.14.0 */ def shouldSupportScala3Classes(): Boolean = _shouldSupportScala3Classes } object ScalaAnnotationIntrospectorModule extends ScalaAnnotationIntrospectorModule private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refHolder: ClassHolder) extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { override def getType(): JavaType = { super.getType() match { case rt: ReferenceType if refHolder.valueClass.isDefined => updateReferenceType(rt, refHolder.valueClass.get) case ct: CollectionLikeType if refHolder.valueClass.isDefined => updateCollectionType(ct, refHolder.valueClass.get) case mt: MapLikeType if refHolder.valueClass.isDefined => updateMapType(mt, refHolder.valueClass.get) case other => other } } private def updateReferenceType(rt: ReferenceType, newRefClass: Class[_]): ReferenceType = { rt.getContentType match { case innerRt: ReferenceType => ReferenceType.upgradeFrom(rt, updateReferenceType(innerRt, newRefClass)) case innerCt: CollectionLikeType => ReferenceType.upgradeFrom(rt, updateCollectionType(innerCt, newRefClass)) case innerMt: MapLikeType => ReferenceType.upgradeFrom(rt, updateMapType(innerMt, newRefClass)) case _ => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(newRefClass)) } } private def updateCollectionType(ct: CollectionLikeType, newRefClass: Class[_]): CollectionLikeType = { ct.getContentType match { case innerRt: ReferenceType => CollectionLikeType.upgradeFrom(ct, updateReferenceType(innerRt, newRefClass)) case innerCt: CollectionLikeType => CollectionLikeType.upgradeFrom(ct, updateCollectionType(innerCt, newRefClass)) case innerMt: MapLikeType => CollectionLikeType.upgradeFrom(ct, updateMapType(innerMt, newRefClass)) case _ => CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(newRefClass)) } } private def updateMapType(mt: MapLikeType, newRefClass: Class[_]): MapLikeType = { mt.getContentType match { case innerRt: ReferenceType => MapLikeType.upgradeFrom(mt, mt.getKeyType, updateReferenceType(innerRt, newRefClass)) case innerCt: CollectionLikeType => MapLikeType.upgradeFrom(mt, mt.getKeyType, updateCollectionType(innerCt, newRefClass)) case innerMt: MapLikeType => MapLikeType.upgradeFrom(mt, mt.getKeyType, updateMapType(innerMt, newRefClass)) case _ => MapLikeType.upgradeFrom(mt, mt.getKeyType, SimpleType.constructUnsafe(newRefClass)) } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy