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

prickle.CompositePicklers.scala Maven / Gradle / Ivy

package prickle

import scala.reflect.ClassTag
import scala.util.{Success, Failure, Try}
import collection.mutable

/**
 * A `CompositePickler[A]` is used to pickle closed class hierarchies under a supertype `A`,
 * where the subclasses' precise static types are lost.
 *
 * Picklers for each concrete subclass `B` must be registered with the composite using the `concreteType[B]` method.
 *
 * CompositePicklers use a more complex serialization format than regular picklers, storing the subclass name
 * under key `CompositePickler.ClassKey` and the pickle body under `CompositePickler.ValueKey`.
 * */

case class CompositePickler[A <: AnyRef](picklers: Map[String, Pickler[_]] = Map.empty) extends Pickler[A] {


  def pickle[P](obj: A, state: PickleState)(implicit config: PConfig[P]): P = {
    if (obj != null) {
      val name = obj.getClass.getName
      val concretePickler = picklers.get(name).getOrElse(throw new IllegalArgumentException(
        s"CompositePickler doesn't know class '$name'. Known classes: ${picklers.keys.mkString(", ")}")
      ).asInstanceOf[Pickler[A]]
      config.makeObject(Seq(
        (CompositePickler.classKey, config.makeString(name)),
        (CompositePickler.valueKey, Pickle(obj, state)(concretePickler, config))))
    } else {
      config.makeNull
    }
  }

  def concreteType[B <: A](implicit p: Pickler[B], subtag: ClassTag[B]): CompositePickler[A] = {
    copy(picklers = this.picklers + (subtag.runtimeClass.getName -> p))
  }
}

/**
 * A `CompositeUnpickler[A]` is used to unpickle closed class hierarchies under a supertype `A`,
 * where the subclasses' precise static types are lost.
 *
 * Unpicklers for each concrete subclass `B` must be registered with the composite using the `concreteType[B]` method.
 * */
case class CompositeUnpickler[A <: AnyRef](unpicklers: Map[String, Unpickler[_]] = Map.empty) extends Unpickler[A] {

  import CompositePickler._

  def unpickle[P](pickle: P, state: mutable.Map[String, Any])(implicit config: PConfig[P]): Try[A] = {
    if (config.isNull(pickle)) {
      Success(null.asInstanceOf[A])
    } else {
      for {
        className <-  config.readObjectFieldStr(pickle, classKey)

        field <- config.readObjectField(pickle, valueKey)

        result <- unpicklers.get(className) match {
          case Some(unpickler) =>
            unpickler.asInstanceOf[Unpickler[A]].unpickle(field, state)(config)
          case None =>
            Failure(new RuntimeException(s"No unpickler for '$className' in: ${unpicklers.keys.mkString(", ")}"))
        }
      } yield result
    }
  }

  def concreteType[B <: A](implicit u: Unpickler[B], subtag: ClassTag[B]): CompositeUnpickler[A] = {
    copy(unpicklers + (subtag.runtimeClass.getName -> u))
  }
}

object CompositePickler {

  def classKey(implicit config: PConfig[_]) = s"${config.prefix}cls"
  def valueKey(implicit config: PConfig[_]) = s"${config.prefix}val"

  def apply[A <: AnyRef] = new PicklerPair[A]()

}

/** Helper for registration of Pickler[B]/Unpickler[B] pairs via `withSubtype[B]`*/
case class PicklerPair[A <: AnyRef](pickler: CompositePickler[A] = new CompositePickler[A](),
                          unpickler: CompositeUnpickler[A] = new CompositeUnpickler[A]()) {

  def concreteType[B <: A](implicit p: Pickler[B], u: Unpickler[B], tag: ClassTag[B]) = {
    copy(pickler = this.pickler.concreteType[B], unpickler = this.unpickler.concreteType[B])
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy