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