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