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

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

There is a newer version: 0.7
Show newest version
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
import cats.syntax.option._

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]]
  }

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

    def merge(patchable: Patchable): Patch[Patchable] =
      apply(patchable)

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

    def merge(patch: Patch[Patchable]): Patch[Patchable] =
      apply(patch)

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

    def build(): Option[Patchable] =
      apply()

    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
    }

    def copy(poptions: (Symbol, Any)*): Patch[Patchable] =
      Patch.of(poptions: _*)(patcher)(this)

    def withDefaults: Patch[Patchable] =
      apply(patcher.defaults)

    def retain(keys: Symbol*) =
      new Patch(poptions.map {
        case (k, v) if keys.contains(k) => k -> v
        case (k, _) => k -> Pnone
      }.toMap, patcher)

    def remove(keys: Symbol*) =
      new Patch(poptions.map {
        case (k, _) if keys.contains(k) => k -> Pnone
        case kv => kv
      }.toMap, patcher)

    /**
      * Transforms this Patch[Patchable] to a Patch[Target]
      *
      * @param transforms   varargs where each element is either:
      *                   - A symbol, representing a symbol we wish to retain on target without transforms.
      *                   - A Typle2 with a symbol and a function, representing a transformation of the value.
      *                     The function is one of:
      *                     - A function of Poption to Poption.
      *                     - A function of value to value.
      *                   - A Tuple2 with:
      *                     - A symbol representing a key in this patch.
      *                     - A symbol representing the corresponding key in the Patch[Target].
      *                   - A Tuple3 with:
      *                     - A symbol representing a key in this patch.
      *                     - A symbol representing the corresponding key in the Patch[Target].
      *                     - A function of value transformation, as above.
      */
    def transform[Target]
    (transforms: Any*)
    (implicit patcher: Patcher[Target]): Patch[Target] = {
      val tmap: Map[Symbol, (Symbol, Function1[Any, Any])] =
        transforms.map {
          case k: Symbol => (k, (k, identity[Any] _))
          case (k: Symbol, f: Function1[Any, Any]) => (k, (k, f))
          case (k: Symbol, t: Symbol) => (k, (t, identity[Any] _))
          case (k: Symbol, t: Symbol, f: Function1[Any, Any]) => (k, (t, f))
        }.toMap
      Patch.of[Target](poptions.collect {
        case (fromKey, fromValue) if tmap.contains(fromKey) =>
          val (toKey, transformation) = tmap(fromKey)
          toKey -> (fromValue match {
            case Pvalue(p) =>
              Try(Pvalue(transformation(p))).toOption.orElse(
                this.patcher.patchers.get(fromKey).flatMap(pat =>
                  Try(transformation(Patch.of[Any](p)(
                    pat.asInstanceOf[Patcher[Any]])).asInstanceOf[Patch[Any]]).
                    toOption.map(Ppatch(_))))
            case Ppatch(pp) =>
              Try(transformation(pp).asInstanceOf[Poption[_]]).toOption.orElse(
                pp().map(transformation).map(Pvalue(_)))
            case popt =>
              popt.some
          }).getOrElse(Pnone)
        case kv@(k, _) if patcher.keys.contains(k) => kv
      }.toMap[Symbol, Any])(patcher)
    }

    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 apply[Patchable](poptions: Map[Symbol, Poption[_]], patcher: Patcher[Patchable]): Patch[Patchable] = new Patch[Patchable](poptions, patcher)

    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)

    def of[Patchable](poptions: (Symbol, Any)*)
                     (implicit patcher: Patcher[Patchable]): Patch[Patchable] =
      of(Map[Symbol, Any](poptions: _*))
  }

  abstract class Patcher[Patchable] extends Serializable {
    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] =
      new 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](
        keys.map(patch.poptions(_) match {
          case Pvalue(p) => p
          case Ppatch(pp) => pp().get
        })))).toOption

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

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

    def diff(patch0: Patch[Patchable], patch1: Patch[Patchable]): Patch[Patchable] =
      new 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]
     //Strict 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] = new Patch(keys.zip(hlistToList[Option[_]](defaultsAsOptions())).map {
        case (k, Some(p)) =>
          k -> Pvalue(p)
        case (k, x) =>
          k -> patchers.get(k).
            map(_.defaults).
            filter(!_.poptions.values.
              forall(_ == Pnone))
            .map(Ppatch(_))
            .getOrElse(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()
      }
  }

  case class PoptionTransformer[From, To](
                                           from: Symbol,
                                           to: Symbol = null,
                                           transform: From => To = null) {
    val to0 = if (null == to) from else to
    val transform0 = if (null == transform) (_: From).asInstanceOf[To] else transform
  }

  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(_) => Right(Pnone[Patchable])
              }
          }
        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