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

zio.schema.CachedDeriver.scala Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package zio.schema

import java.util.concurrent.{ ConcurrentHashMap, ConcurrentMap }

import scala.reflect.ClassTag

import zio.Chunk
import zio.schema.CachedDeriver.{ Cache, CacheKey }
import zio.schema.Deriver.WrappedF

private[schema] class CachedDeriver[F[_]] private (deriver: Deriver[F], val cache: Cache[F]) extends Deriver[F] {

  override def deriveRecord[A](
    record: Schema.Record[A],
    fields: => Chunk[WrappedF[F, _]],
    summoned: => Option[F[A]]
  ): F[A] =
    cached(record) {
      deriver.deriveRecord(record, fields, summoned)
    }

  override def deriveEnum[A](`enum`: Schema.Enum[A], cases: => Chunk[WrappedF[F, _]], summoned: => Option[F[A]]): F[A] =
    cached(`enum`) {
      deriver.deriveEnum(`enum`, cases, summoned)
    }

  override def derivePrimitive[A](st: StandardType[A], summoned: => Option[F[A]]): F[A] =
    cached(Schema.primitive(st)) {
      deriver.derivePrimitive(st, summoned)
    }

  override def derivePrimitiveAlias[A: ClassTag, U](st: StandardType[U], summoned: => Option[F[A]]): F[A] = {
    val key = CacheKey.PrimitiveAlias(st, implicitly[ClassTag[A]])
    cache.get(key) match {
      case None    => cache.put(key, deriver.derivePrimitiveAlias[A, U](st, summoned))
      case Some(g) => g
    }
  }

  override def deriveOption[A](
    option: Schema.Optional[A],
    inner: => F[A],
    summoned: => Option[F[Option[A]]]
  ): F[Option[A]] =
    cached(option) {
      deriver.deriveOption(option, inner, summoned)
    }

  override def deriveSequence[C[_], A](
    sequence: Schema.Sequence[C[A], A, _],
    inner: => F[A],
    summoned: => Option[F[C[A]]]
  ): F[C[A]] =
    cached(sequence) {
      deriver.deriveSequence(sequence, inner, summoned)
    }

  override def deriveMap[K, V](
    map: Schema.Map[K, V],
    key: => F[K],
    value: => F[V],
    summoned: => Option[F[Map[K, V]]]
  ): F[Map[K, V]] =
    cached(map) {
      deriver.deriveMap(map, key, value, summoned)
    }

  override def deriveEither[A, B](
    either: Schema.Either[A, B],
    left: => F[A],
    right: => F[B],
    summoned: => Option[F[Either[A, B]]]
  ): F[Either[A, B]] =
    cached(either) {
      deriver.deriveEither(either, left, right, summoned)
    }

  override def deriveSet[A](set: Schema.Set[A], inner: => F[A], summoned: => Option[F[Set[A]]]): F[Set[A]] =
    cached(set) {
      deriver.deriveSet(set, inner, summoned)
    }

  override def deriveTransformedRecord[A, B](
    record: Schema.Record[A],
    transform: Schema.Transform[A, B, _],
    fields: => Chunk[WrappedF[F, _]],
    summoned: => Option[F[B]]
  ): F[B] =
    cached(transform) {
      deriver.deriveTransformedRecord(record, transform, fields, summoned)
    }

  override def deriveTupleN[T](
    schemasAndInstances: => Chunk[(Schema[_], WrappedF[F, _])],
    summoned: => Option[F[T]]
  ): F[T] = {
    val zipped = zippedTupleSchema(schemasAndInstances.map(_._1))
    cached(zipped.asInstanceOf[Schema[T]]) {
      deriver.deriveTupleN(schemasAndInstances, summoned)
    }
  }

  private def cached[A](schema: Schema[A])(f: => F[A]): F[A] = {
    val key = CacheKey.fromSchema(schema)
    cache.get(key) match {
      case None    => cache.put(key, f)
      case Some(g) => g
    }
  }

  private def zippedTupleSchema(schemas: Chunk[Schema[_]]): Schema[_] =
    schemas.reduceLeft((a, b) => a.zip(b))
}

private[schema] object CachedDeriver {
  sealed trait CacheKey[A]

  object CacheKey {
    final case class Primitive[A](standardType: StandardType[A])                                extends CacheKey[A]
    final case class PrimitiveAlias[A, U](standardType: StandardType[U], classTag: ClassTag[A]) extends CacheKey[A]
    final case class WithId[A](typeId: TypeId)                                                  extends CacheKey[A]
    final case class WithIdentityObject[A](inner: CacheKey[_], id: Any)                         extends CacheKey[A]
    final case class Optional[A](key: CacheKey[A])                                              extends CacheKey[A]
    final case class Either[A, B](leftKey: CacheKey[A], rightKey: CacheKey[B])                  extends CacheKey[Either[A, B]]
    final case class Fallback[A, B](leftKey: CacheKey[A], rightKey: CacheKey[B])                extends CacheKey[Fallback[A, B]]
    final case class Tuple2[A, B](leftKey: CacheKey[A], rightKey: CacheKey[B])                  extends CacheKey[(A, B)]
    final case class Set[A](element: CacheKey[A])                                               extends CacheKey[Set[A]]
    final case class Map[K, V](key: CacheKey[K], valuew: CacheKey[V])                           extends CacheKey[Map[K, V]]
    final case class NonEmptyMap[K, V](key: CacheKey[K], valuew: CacheKey[V])                   extends CacheKey[NonEmptyMap[K, V]]
    final case class Misc[A](schema: Schema[A])                                                 extends CacheKey[A]

    def fromStandardType[A](st: StandardType[A]): CacheKey[A] = Primitive(st)

    def fromSchema[A](schema: Schema[A]): CacheKey[A] =
      schema match {
        case e: Schema.Enum[_]                     => WithId(e.id)
        case record: Schema.Record[_]              => WithId(record.id)
        case seq: Schema.Sequence[_, _, _]         => WithIdentityObject(fromSchema(seq.elementSchema), seq.identity)
        case seq: Schema.NonEmptySequence[_, _, _] => WithIdentityObject(fromSchema(seq.elementSchema), seq.identity)
        case set: Schema.Set[_]                    => Set(fromSchema(set.elementSchema)).asInstanceOf[CacheKey[A]]
        case map: Schema.Map[_, _] =>
          Map(fromSchema(map.keySchema), fromSchema(map.valueSchema)).asInstanceOf[CacheKey[A]]
        case map: Schema.NonEmptyMap[_, _] =>
          Map(fromSchema(map.keySchema), fromSchema(map.valueSchema)).asInstanceOf[CacheKey[A]]
        case Schema.Transform(inner, _, _, _, identity) => WithIdentityObject(fromSchema(inner), identity)
        case Schema.Primitive(standardType, _)          => fromStandardType(standardType)
        case optional: Schema.Optional[_]               => Optional(fromSchema(optional.schema)).asInstanceOf[CacheKey[A]]
        case tuple: Schema.Tuple2[_, _] =>
          Tuple2(fromSchema(tuple.left), fromSchema(tuple.right)).asInstanceOf[CacheKey[A]]
        case either: Schema.Either[_, _] =>
          Either(fromSchema(either.leftSchema), fromSchema(either.rightSchema)).asInstanceOf[CacheKey[A]]
        case fallback: Schema.Fallback[_, _] =>
          Fallback(fromSchema(fallback.left), fromSchema(fallback.right)).asInstanceOf[CacheKey[A]]
        case Schema.Lazy(schema0) => fromSchema(schema0())
        case Schema.Dynamic(_)    => Misc(schema)
        case Schema.Fail(_, _)    => Misc(schema)
      }
  }

  class Cache[F[_]] private[CachedDeriver] (map: ConcurrentMap[CacheKey[_], Any]) {

    def get[A](schema: CacheKey[A]): Option[F[A]] =
      Option(map.get(schema)).map(_.asInstanceOf[F[A]])

    def put[A](schema: CacheKey[A], value: F[A]): F[A] = {
      map.put(schema, value)
      value
    }

    def size: Int = map.size()
  }

  private[schema] def apply[F[_]](deriver: Deriver[F], cache: Cache[F]): Deriver[F] =
    new CachedDeriver[F](deriver, cache)

  def createCache[F[_]]: Cache[F] =
    new Cache[F](new ConcurrentHashMap[CacheKey[_], Any]())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy