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

no.kodeworks.kvarg.patch.package.scala Maven / Gradle / Ivy

package no.kodeworks.kvarg

import io.circe._
import generic.semiauto
import Decoder._
import shapeless.ops.hlist.LiftAll
import shapeless.ops.record.UnzipFields
import shapeless.{::, Default, DepFn0, HList, HNil, LabelledGeneric, Lazy, LowPriority, Refute, Strict, Typeable, cachedImplicit}
import util._

import scala.util.Try

package object patch {

  sealed trait Poption[P] extends Serializable

  case class Pvalue[P](p: P) extends Poption[P]

  case class Ppatch[P](pp: Patch[P]) extends Poption[P]

  case class Pstring[P](s: String) extends Poption[P]

  case object Pnone extends Poption[Nothing] {
    def apply[P] = Pnone.asInstanceOf[Poption[P]]
  }

  case class Patch[Patchable](
                               poptions: Map[Symbol, Poption[_]],
                               patcher: Patcher[Patchable]
                             ) {
    def apply(patchable: Patchable): Patch[Patchable] =
      patcher(this, patchable)

    def apply(patch: Patch[Patchable]): Patch[Patchable] =
      patcher(this, patch)

    def apply(): Option[Patchable] =
      patcher(this)

    def diff(patch: Patch[Patchable]): Patch[Patchable] =
      patcher.diff(this, patch)

    def get[P](key: Symbol): Option[P] = poptions.get(key).flatMap {
      case p: Pvalue[P] => Some(p.p)
      case _ => None
    }

    override def toString() = s"Patch(${poptions.mkString(", ")})"

    def canEqual(other: Any): Boolean = other.isInstanceOf[Patch[_]]

    override def equals(other: Any): Boolean = other match {
      case that: Patch[_] =>
        (that canEqual this) &&
          poptions == that.poptions
      case _ => false
    }

    override def hashCode(): Int = {
      val state = Seq(poptions)
      state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
    }
  }

  object Patch {
    def of[Patchable](patchable: Patchable)
                     (implicit patcher: Patcher[Patchable]): Patch[Patchable] =
      patcher(patchable)

    def of[Patchable](poptions: Map[Symbol, Any], withDefaults: Boolean = false)
                     (implicit patcher: Patcher[Patchable]): Patch[Patchable] =
      patcher(poptions, withDefaults)
  }

  abstract class Patcher[Patchable] {
    type PatchableFields <: HList
    val keys: List[Symbol]
    val fields: LabelledGeneric.Aux[Patchable, PatchableFields]
    val patchers: Map[Symbol, Patcher[_]]
    val types: Map[Symbol, Typeable[_]]
    val defaults: Patch[Patchable]

    def apply(patch0: Patch[Patchable], patchable: Patchable): Patch[Patchable] =
      apply(patch0, apply(patchable))

    def apply(patch0: Patch[Patchable], patch1: Patch[Patchable]): Patch[Patchable] =
      Patch(patch0.poptions.keys.map(k => (k, (patch0.poptions(k), patch1.poptions(k)) match {
        case (p0: Pvalue[_], _) => p0
        case (p0: Ppatch[_], p1: Pvalue[_]) if patchers.contains(k) =>
          Ppatch(p0.pp.asInstanceOf[Patch[Any]](Patch.of[Any](p1.p)(patchers(k).asInstanceOf[Patcher[Any]])))
        case (p0: Ppatch[_], _: Pvalue[_]) => p0
        case (p0: Ppatch[Patchable], p1: Ppatch[Patchable]) => Ppatch(p0.pp(p1.pp))
        case (p0: Ppatch[_], _) => p0
        case (p0: Pstring[_], _) => p0
        case (_, p1) => p1
      })).toMap, this)

    def apply(patch: Patch[Patchable]): Option[Patchable] =
      Try(fields.from(
        listToHList[PatchableFields](
          patch.poptions.values.collect {
            case Pvalue(p) => p
            case Ppatch(pp) => pp().get
          }.toList))).toOption

    def apply(patchable: Patchable): Patch[Patchable] = {
      val values0 = hlistToList[Any](fields.to(patchable)).map(Pvalue(_))
      Patch(keys.zip(values0).toMap, this)
    }

    def apply(poptions: Map[Symbol, Any], withDefaults: Boolean = false): Patch[Patchable] = {
      val popts = Patch(keys.map { key =>
        key -> poptions.get(key).map {
          case p: Poption[_] => p
          case s: String =>
            this.types(key).cast(s)
              .map(Pvalue(_))
              .getOrElse(Pstring(s))
          case pp: Patch[_] => Ppatch(pp)
          case v => Pvalue(v)
        }.getOrElse(Pnone)
      }.toMap, this)
      if (withDefaults)
        popts(defaults)
      else popts
    }

    def diff(patch0: Patch[Patchable], patch1: Patch[Patchable]): Patch[Patchable] =
      Patch(patch0.poptions.keys.map(k => (k, (patch0.poptions(k), patch1.poptions(k)) match {
        case (p0, p1) if p0 == p1 => Pnone
        case (p0: Pvalue[_], _) => p0
        case (p0: Ppatch[_], p1: Pvalue[_]) if patchers.contains(k) =>
          Ppatch(p0.pp.asInstanceOf[Patch[Any]].diff(Patch.of[Any](p1.p)(patchers(k).asInstanceOf[Patcher[Any]])))
        case (p0: Ppatch[_], _: Pvalue[_]) => p0
        case (p0: Ppatch[Patchable], p1: Ppatch[Patchable]) => Ppatch(p0.pp.diff(p1.pp))
        case (p0: Ppatch[_], _) => p0
        case (p0: Pstring[_], _) => p0
        case (_, _) => Pnone
      })).toMap, this)
  }

  object Patcher {
    implicit def patcher[
    Patchable
    , PatchableFields0 <: HList
    , PatchableKeys0 <: HList
    , PatchableValues0 <: HList
    , PatcherOptions <: HList
    , Typeables0 <: HList
    ]
    (implicit
     fields0: LabelledGeneric.Aux[Patchable, PatchableFields0]
     , keysValues: UnzipFields.Aux[PatchableFields0, PatchableKeys0, PatchableValues0]
     , patcherOptions: Patchers.Aux[PatchableValues0, PatcherOptions]
     //enables use of LiftAll[Patcher] elsewhere
     , typeables: Strict[LiftAll.Aux[Typeable, PatchableValues0, Typeables0]]
     , defaultsAsOptions: Default.AsOptions[Patchable]
    )
    : Patcher[Patchable] = new Patcher[Patchable] {
      override type PatchableFields = PatchableFields0
      override val keys = hlistToList[Symbol](keysValues.keys())
      override val fields = fields0
      override val patchers =
        keys.zip(hlistToList[Option[Patcher[_]]](patcherOptions())).collect {
          case (k, Some(p)) => (k, p)
        }.toMap
      override val types = keys.zip(hlistToList[Typeable[_]](typeables.value.instances)).toMap
      override val defaults: Patch[Patchable] = Patch(keys.zip(hlistToList[Option[_]](defaultsAsOptions()).map {
        case Some(p) => Pvalue(p)
        case _ => Pnone
      }).toMap, this)
    }

    def apply[Patchable](implicit patcher: Patcher[Patchable]): Patcher[Patchable] = patcher
  }


  trait Patchers[L <: HList] extends DepFn0 with Serializable {
    type Out <: HList
  }

  object Patchers {
    type Aux[L <: HList, Out0 <: HList] = Patchers[L] {type Out = Out0}

    implicit def hnilPatchers[L <: HNil]: Aux[L, HNil] = new Patchers[L] {
      type Out = HNil

      def apply(): Out = HNil
    }

    implicit def hlistPatchers[K, Rest <: HList]
    (implicit
     patchersRest: Patchers[Rest]
     , patcherHead: Strict[Patcher[K]] = null //TODO default impl for non case classes?
    ): Aux[K :: Rest, Option[Patcher[K]] :: patchersRest.Out] =
      new Patchers[K :: Rest] {
        type Out = Option[Patcher[K]] :: patchersRest.Out

        def apply(): Out =
          Option(patcherHead).map(_.value) :: patchersRest()
      }
  }

  trait decoderLP {
    implicit def decodePatch[Patchable]: Decoder[Patch[Patchable]] = macro CodecMacros.materializeDecoder[Patchable]

    implicit def decodePoptions[Patchable]
    (implicit pd: Decoder[Patchable]
    ): Decoder[Poption[Patchable]] = {
      withReattempt {
        case c: HCursor =>
          if (!c.focus.forall(!_.isNull)) Right(Pnone[Patchable])
          else pd(c) match {
            case Right(a) => Right(Pvalue[Patchable](a))
            case Left(df) if df.history.isEmpty => Right(Pnone[Patchable])
            case Left(_) =>
              Decoder[String].apply(c) match {
                case Right(a) => Right(Pstring[Patchable](a))
                case Left(df) => Left(df)
              }
          }
        case c: FailedCursor =>
          if (!c.incorrectFocus)
            Right(Pnone[Patchable])
          else
            Left(DecodingFailure("[A]Poptions[A]", c.history))
      }
    }
  }

  object decoder extends decoderLP {
    implicit def decodePpatch[Patchable]
    (implicit ppd: Decoder[Patch[Patchable]]
    ): Decoder[Poption[Patchable]] = {
      withReattempt {
        case c: HCursor =>
          if (!c.focus.forall(!_.isNull)) Right(Pnone[Patchable])
          else ppd(c) match {
            case Right(a) => Right(Ppatch[Patchable](a))
            case Left(df) => Left(df)
          }
        case c: FailedCursor =>
          if (!c.incorrectFocus)
            Right(Pnone[Patchable])
          else
            Left(DecodingFailure("[A]Poptions[A]", c.history))
      }
    }

    implicit final val decodePoptionChar: Decoder[Poption[Char]] = cachedImplicit
    implicit final val decodePoptionShort: Decoder[Poption[Short]] = cachedImplicit
    implicit final val decodePoptionInt: Decoder[Poption[Int]] = cachedImplicit
    implicit final val decodePoptionLong: Decoder[Poption[Long]] = cachedImplicit
    implicit final val decodePoptionFloat: Decoder[Poption[Float]] = cachedImplicit
    implicit final val decodePoptionDouble: Decoder[Poption[Double]] = cachedImplicit
    implicit final val decodePoptionString: Decoder[Poption[String]] = cachedImplicit
  }

  trait encoderLP {
    implicit def encodePatch[Patchable]
    : ObjectEncoder[Patch[Patchable]] = macro CodecMacros.materializeEncoder[Patchable]

    implicit def encodePoptions[Patchable]
    (implicit pe: Encoder[Patchable]
    ): Encoder[Poption[Patchable]] = {
      case Pvalue(p) => pe(p)
      case Pstring(s) => Encoder.encodeString(s)
      case _ => Json.Null
    }
  }

  object encoder extends encoderLP {

    implicit def encodePpatch[Patchable]
    (implicit ppe: Encoder[Patch[Patchable]]
    ): Encoder[Ppatch[Patchable]] = {
      case Ppatch(pp) => ppe(pp)
    }

    implicit val encodePoptionChar: Encoder[Poption[Char]] = cachedImplicit
    implicit val encodePoptionShort: Encoder[Poption[Short]] = cachedImplicit
    implicit val encodePoptionInt: Encoder[Poption[Int]] = cachedImplicit
    implicit val encodePoptionLong: Encoder[Poption[Long]] = cachedImplicit
    implicit val encodePoptionFloat: Encoder[Poption[Float]] = cachedImplicit
    implicit val encodePoptionDouble: Encoder[Poption[Double]] = cachedImplicit
    implicit val encodePoptionString: Encoder[Poption[String]] = cachedImplicit
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy