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

pl.touk.nussknacker.engine.extension.ToMapConversionExt.scala Maven / Gradle / Ivy

The newest version!
package pl.touk.nussknacker.engine.extension

import cats.data.ValidatedNel
import cats.implicits.catsSyntaxValidatedId
import pl.touk.nussknacker.engine.api.generics.{GenericFunctionTypingError, MethodTypeInfo}
import pl.touk.nussknacker.engine.api.typed.typing._
import pl.touk.nussknacker.engine.definition.clazz.{FunctionalMethodDefinition, MethodDefinition}
import pl.touk.nussknacker.engine.extension.CastOrConversionExt.{canBeMethodName, orNullSuffix, toMethodName}
import pl.touk.nussknacker.engine.spel.internal.ConversionHandler

import java.lang.{Boolean => JBoolean}
import java.util.{Collection => JCollection, HashMap => JHashMap, Map => JMap, Set => JSet}
import scala.annotation.tailrec

object ToMapConversionExt extends ConversionExt(ToMapConversion) {
  private val booleanTyping = Typed.typedClass[Boolean]
  private val mapTyping     = Typed.genericTypeClass[JMap[_, _]](List(Unknown, Unknown))

  private val isMapMethodDefinition = FunctionalMethodDefinition(
    typeFunction = (invocationTarget, _) => ToMapConversion.typingFunction(invocationTarget).map(_ => booleanTyping),
    signature = MethodTypeInfo.noArgTypeInfo(booleanTyping),
    name = s"${canBeMethodName}Map",
    description = Some("Check whether can be convert to a map")
  )

  private val toMapDefinition = FunctionalMethodDefinition(
    typeFunction = (invocationTarget, _) => ToMapConversion.typingFunction(invocationTarget),
    signature = MethodTypeInfo.noArgTypeInfo(mapTyping),
    name = s"${toMethodName}Map",
    description = Option("Convert to a map or throw exception in case of failure")
  )

  private val toMapOrNullDefinition = FunctionalMethodDefinition(
    typeFunction = (invocationTarget, _) => ToMapConversion.typingFunction(invocationTarget),
    signature = MethodTypeInfo.noArgTypeInfo(mapTyping),
    name = s"${toMethodName}Map${orNullSuffix}",
    description = Option("Convert to a map or null in case of failure")
  )

  override protected def definitions(): List[MethodDefinition] = List(
    isMapMethodDefinition,
    toMapDefinition,
    toMapOrNullDefinition,
  )

}

object ToMapConversion extends ToCollectionConversion[JMap[_, _]] {

  private val keyName          = "key"
  private val valueName        = "value"
  private val keyAndValueNames = JSet.of(keyName, valueName)

  override val typingResult: TypingResult = Typed.genericTypeClass(resultTypeClass, List(Unknown, Unknown))

  override val typingFunction: TypingResult => ValidatedNel[GenericFunctionTypingError, TypingResult] =
    invocationTarget =>
      invocationTarget.withoutValue match {
        case TypedClass(_, List(TypedObjectTypingResult(fields, _, _)))
            if fields.contains(keyName) && fields.contains(valueName) =>
          val params = List(fields.get(keyName), fields.get(valueName)).flatten
          Typed.genericTypeClass[JMap[_, _]](params).validNel
        case TypedClass(_, List(TypedObjectTypingResult(_, _, _))) =>
          GenericFunctionTypingError.OtherError("List element must contain 'key' and 'value' fields").invalidNel
        case Unknown => Typed.genericTypeClass[JMap[_, _]](List(Unknown, Unknown)).validNel
        case _       => GenericFunctionTypingError.ArgumentTypeError.invalidNel
      }

  @tailrec
  override def convertEither(value: Any): Either[Throwable, JMap[_, _]] =
    value match {
      case m: JMap[_, _] => Right(m)
      case a: Array[_]   => convertEither(ConversionHandler.convertArrayToList(a))
      case c: JCollection[JMap[_, _] @unchecked] if canConvertToMap(c) =>
        val map = new JHashMap[Any, Any]()
        c.forEach(e => map.put(e.get(keyName), e.get(valueName)))
        Right(map)
      case x => Left(new IllegalArgumentException(s"Cannot convert: $x to a Map"))
    }

  // We could leave underlying method using convertEither as well but this implementation is faster
  override def canConvert(value: Any): JBoolean = value match {
    case _: JMap[_, _]     => true
    case c: JCollection[_] => canConvertToMap(c)
    case a: Array[_]       => canConvertToMap(ConversionHandler.convertArrayToList(a))
    case _                 => false
  }

  private def canConvertToMap(c: JCollection[_]): Boolean = c.isEmpty || c
    .stream()
    .allMatch {
      case m: JMap[_, _] if !m.isEmpty => m.keySet().containsAll(keyAndValueNames)
      case _                           => false
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy