Skip to content
This repository has been archived by the owner on Feb 20, 2019. It is now read-only.

Commit

Permalink
Add custom runtime generators for Either
Browse files Browse the repository at this point in the history
* Add custom runtime generators for Either and tests checking the new
  custom runtime generation.

* Make `AnyPicklerUnpickler` deal with more corner cases.

* Modify PicklingProtocol in sandbox-test and reuse it for both formats.

* Add error for disabled runtime generation.

* Improve the generator helper, make some renamings and add more
  documentation.
  • Loading branch information
jvican committed May 5, 2016
1 parent 4121da4 commit 3bc60b8
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 141 deletions.
12 changes: 11 additions & 1 deletion core/src/main/scala/scala/pickling/PicklingErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ private[pickling] object Feedback {
def failedParsingMsg(x: String, format: String) =
s"Failed to parse $x as $format."

def disabledRuntimeGenerationMsg(tpe: String, failed: String) =
s"Couldn't create $failed for $tpe because runtime generation is disabled."

}

object PicklingErrors {
Expand Down Expand Up @@ -55,7 +58,14 @@ object PicklingErrors {
failedGenerationMsg(tpe, concreteTpe, "unpickler"), cause
)

/** Represent any error related to parsgin. */
/** Exception thrown when it's not possible to generated a [[Pickler]]
* or [[Unpickler]] at runtime because runtime generation has been disabled.*/
final case class RuntimeGenerationDisabled(tpe: String, failed: String)
extends UnsupportedOperationException(
disabledRuntimeGenerationMsg(tpe, failed)
)

/** Represent any error related to parsing. */
class ParsingException(msg: String) extends BasePicklingException(msg)

/** Exception thrown when the parsing of a message is not successful.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import scala.pickling.spi.{PicklerRegistry, RuntimePicklerGenerator}
final class DefaultPicklerRegistry(generator: RuntimePicklerGenerator)
extends PicklerRegistry with RuntimePicklerRegistry {

type PicklerGen = FastTypeTag[_] => Pickler[_]
type UnpicklerGen = FastTypeTag[_] => Unpickler[_]
type PicklerGenerator = FastTypeTag[_] => Pickler[_]
type UnpicklerGenerator = FastTypeTag[_] => Unpickler[_]

private val picklerMap: mutable.Map[String, Pickler[_]] = new TrieMap[String, Pickler[_]]
private val picklerGenMap: mutable.Map[String, PicklerGen] = new TrieMap[String, PicklerGen]
private val picklerGenMap: mutable.Map[String, PicklerGenerator] = new TrieMap[String, PicklerGenerator]
private val unpicklerMap: mutable.Map[String, Unpickler[_]] = new TrieMap[String, Unpickler[_]]
private val unpicklerGenMap: mutable.Map[String, UnpicklerGen] = new TrieMap[String, UnpicklerGen]
private val unpicklerGenMap: mutable.Map[String, UnpicklerGenerator] = new TrieMap[String, UnpicklerGenerator]

registerRuntimePicklersAtInit()

Expand Down Expand Up @@ -162,13 +162,10 @@ final class DefaultPicklerRegistry(generator: RuntimePicklerGenerator)
*/
private[pickling] def dumpStateTo(r: PicklerRegistry): Unit = {

type AnyPicklerGen = FastTypeTag[_] => Pickler[Any]
type AnyUnpicklerGen = FastTypeTag[_] => Unpickler[Any]

for(p <- picklerMap) r.registerPickler(p._1, p._2.asInstanceOf[Pickler[Any]])
for(p <- picklerGenMap) r.registerPicklerGenerator(p._1, p._2.asInstanceOf[AnyPicklerGen])
for(p <- picklerGenMap) r.registerPicklerGenerator(p._1, p._2.asInstanceOf[PicklerGen[Any]])
for(u <- unpicklerMap) r.registerUnpickler(u._1, u._2.asInstanceOf[Unpickler[Any]])
for(u <- unpicklerGenMap) r.registerUnpicklerGenerator(u._1, u._2.asInstanceOf[AnyUnpicklerGen])
for(u <- unpicklerGenMap) r.registerUnpicklerGenerator(u._1, u._2.asInstanceOf[UnpicklerGen[Any]])

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import scala.pickling.spi.PicklerRegistry
* Related to [[scala.pickling.runtime.CustomRuntime]].
* Note: Currently this only handles Tuple2s.
*/
trait RuntimePicklerRegistry extends PicklerRegistry with CustomRuntime {
trait RuntimePicklerRegistry extends CustomRuntime {
this: PicklerRegistry =>

val tupleGenerators = (tuplePicklerGenerator, tupleUnpicklerGenerator)

Expand Down
34 changes: 23 additions & 11 deletions core/src/main/scala/scala/pickling/pickler/Any.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,44 @@ import scala.reflect.runtime.currentMirror
object AnyPicklerUnpickler extends AbstractPicklerUnpickler[Any]
with AutoRegister[Any] {

final val nullPickler = Defaults.nullPickler.asInstanceOf[Pickler[Any]]

override def tag: FastTypeTag[Any] = FastTypeTag.Any

/** Pickle [[Any]] by getting its class at runtime and looking
* up the correct [[Pickler]] for that class if available or
* generate it.
*
* Don't use [[AnyPicklerUnpickler]] for pickling classes with generic
* types. Otherwise, it will fail because of the type erasure
* and the lookup will replace the unknown type by [[Any]].
* types. Otherwise, it will fail because of the type erasure.
* The lookup will replace the unknown type parameters by [[Any]].
*/
override def pickle(picklee: Any, builder: PBuilder): Unit = {
val clazz = picklee.getClass
val classLoader = this.getClass.getClassLoader
GRL.lock()
val tag = try FastTypeTag.makeRaw(clazz)
finally GRL.unlock()
val p = currentRuntime.picklers.genPickler(classLoader, clazz, tag)
p.asInstanceOf[Pickler[Any]].pickle(picklee, builder)

// Use nullPickler if null, get pickler otherwise
val pickler = if (picklee == null) nullPickler else {
val clazz = picklee.getClass
val classLoader = this.getClass.getClassLoader
GRL.lock()
val tag = try FastTypeTag.makeRaw(clazz)
finally GRL.unlock()
val p = currentRuntime.picklers.genPickler(classLoader, clazz, tag)
p.asInstanceOf[Pickler[Any]]
}

pickler.pickle(picklee, builder)

}

/** Unpickle something as [[Any]] by looking up registered
* unpicklers for [[tag]] or using runtime unpickler generation.
*/
def unpickle(tag: String, reader: PReader): Any = {
val actualUnpickler = currentRuntime.picklers.genUnpickler(currentMirror, tag)
actualUnpickler.unpickle(tag, reader)
if (reader.atPrimitive) reader.readPrimitive()
else {
val actualUnpickler = currentRuntime.picklers.genUnpickler(currentMirror, tag)
actualUnpickler.unpickle(tag, reader)
}
}

override def toString = "AnyPicklerUnpickler"
Expand Down
187 changes: 149 additions & 38 deletions core/src/main/scala/scala/pickling/pickler/Either.scala
Original file line number Diff line number Diff line change
@@ -1,69 +1,180 @@
package scala.pickling
package pickler

import scala.pickling.PicklingErrors.TypeMismatch

/** Generate [[Pickler]]s and [[Unpickler]]s for [[Either]]
* and its subclasses [[Right]] and [[Left]].
*/
trait EitherPicklers {
// TODO(jsuereth) - Register pickler generators

implicit def pickleUnpickleLeft[L, R](implicit lp: Pickler[L], lu: Unpickler[L],
t: FastTypeTag[Left[L,R]]): AbstractPicklerUnpickler[Left[L, R]] =
new AbstractPicklerUnpickler[Left[L, R]] with AutoRegister[Left[L, R]] {
override lazy val tag: FastTypeTag[Left[L, R]] = t
override def pickle(picklee: Left[L, R], builder: PBuilder): Unit = {
builder.beginEntry(picklee, tag)
if(lp.tag.isEffectivelyPrimitive) builder.hintElidedType(lp.tag)
builder.putField("a", b => lp.pickle(picklee.a, b))
builder.endEntry()
}
override def unpickle(tag: String, reader: PReader): Any = {
if (t.key == tag) {
val rr = reader.readField("a")
if(lp.tag.isEffectivelyPrimitive) rr.hintElidedType(lp.tag)
Left(lu.unpickleEntry(rr).asInstanceOf[R])
} else throw new PicklingException(s"LeftUnpickler can't unpickle: $tag")
}
override def toString = s"LeftPicklerUnpickler($tag)"
}

implicit def pickleUnpickleRight[L,R](implicit rp: Pickler[R], ru: Unpickler[R],
t: FastTypeTag[Right[L,R]]): AbstractPicklerUnpickler[Right[L,R]] =
trait EitherPicklers extends EitherPicklersRuntime with GeneratorRegistry {

implicit def pickleUnpickleLeft[L, R]
(implicit lp: Pickler[L], lu: Unpickler[L],
t: FastTypeTag[Left[L,R]]): AbstractPicklerUnpickler[Left[L, R]] = {
new AbstractPicklerUnpickler[Left[L, R]] with AutoRegister[Left[L, R]] {
override lazy val tag: FastTypeTag[Left[L, R]] = t

override def pickle(picklee: Left[L, R], builder: PBuilder): Unit = {
builder.beginEntry(picklee, tag)
if (lp.tag.isEffectivelyPrimitive) builder.hintElidedType(lp.tag)
builder.putField("a", b => lp.pickle(picklee.a, b))
builder.endEntry()
}

override def unpickle(tag: String, reader: PReader): Any = {
if (t.key == tag) {
val rr = reader.readField("a")
if (lp.tag.isEffectivelyPrimitive) rr.hintElidedType(lp.tag)
Left(lu.unpickleEntry(rr).asInstanceOf[R])
} else throw TypeMismatch(List(t), List(FastTypeTag(tag)))
}

override def toString = s"LeftPicklerUnpickler($tag)"
}
}

implicit def pickleUnpickleRight[L,R]
(implicit rp: Pickler[R], ru: Unpickler[R],
t: FastTypeTag[Right[L,R]]): AbstractPicklerUnpickler[Right[L,R]] = {
new AbstractPicklerUnpickler[Right[L, R]] with AutoRegister[Right[L, R]] {
override lazy val tag: FastTypeTag[Right[L, R]] = t

override def pickle(picklee: Right[L, R], builder: PBuilder): Unit = {
builder.beginEntry(picklee, tag)
if(rp.tag.isEffectivelyPrimitive) builder.hintElidedType(rp.tag)
if (rp.tag.isEffectivelyPrimitive) builder.hintElidedType(rp.tag)
builder.putField("b", b => rp.pickle(picklee.b, b))
builder.endEntry()
}

override def unpickle(tag: String, reader: PReader): Any = {
if (t.key == tag) {
val rr = reader.readField("b")
if(rp.tag.isEffectivelyPrimitive) rr.hintElidedType(rp.tag)
if (rp.tag.isEffectivelyPrimitive) rr.hintElidedType(rp.tag)
Right(ru.unpickleEntry(rr).asInstanceOf[R])
} else throw new PicklingException(s"RightUnpickler can't unpickle: $tag")
} else throw TypeMismatch(List(t), List(FastTypeTag(tag)))
}

override def toString = s"RightPicklerUnpickler($tag)"
}
}

implicit def pickleUnpickleEither[L,R](implicit rp: Pickler[Right[L,R]], ru: Unpickler[Right[L, R]],
lp: Pickler[Left[L,R]], lu: Unpickler[Left[L, R]],
t: FastTypeTag[Either[L,R]]): AbstractPicklerUnpickler[Either[L,R]] =
implicit def pickleUnpickleEither[L,R]
(implicit rp: Pickler[Right[L,R]], ru: Unpickler[Right[L, R]],
lp: Pickler[Left[L,R]], lu: Unpickler[Left[L, R]],
t: FastTypeTag[Either[L,R]]): AbstractPicklerUnpickler[Either[L,R]] = {
new AbstractPicklerUnpickler[Either[L, R]] with AutoRegister[Either[L, R]] {
override def pickle(picklee: Either[L, R], builder: PBuilder): Unit = {
picklee match {
case l: Left[L,R] => lp.pickle(l, builder)
case r: Right[L,R] => rp.pickle(r, builder)
case l: Left[L, R] => lp.pickle(l, builder)
case r: Right[L, R] => rp.pickle(r, builder)
}
}

override def unpickle(tag: String, reader: PReader): Any = {
if(tag == rp.tag.key) ru.unpickle(tag,reader)
else if(tag == lp.tag.key) lu.unpickle(tag, reader)
else throw new PicklingException(s"Unknown type tag for Either: $tag")
if (tag == rp.tag.key) ru.unpickle(tag, reader)
else if (tag == lp.tag.key) lu.unpickle(tag, reader)
else throw TypeMismatch(List(rp.tag, lp.tag), List(FastTypeTag(tag)))
}

override def tag: FastTypeTag[Either[L, R]] = t
override def toString = s"EitherPicklerUnpickler($t)"

override def toString = s"EitherPicklerUnpickler($tag)"
}
}

locally {

registerPicklerAsGen(RuntimeLeftPicklerUnpickler)
registerPicklerAsGen(RuntimeRightPicklerUnpickler)
registerPicklerAsGen(RuntimeEitherPicklerUnpickler)

}

}

trait EitherPicklersRuntime extends GeneratorHelper {

/** Pickle as [[Any]] because the scala pickling
* use [[Any]] as a placeholder for the type params.
*/
private def pickleAsAny[S <: Either[_, _]]
(picklee: S, fullTpe: FastTypeTag[S],
name: String, field: Any, builder: PBuilder) = {

builder.beginEntry(picklee, fullTpe)
builder.putField(name, { b =>
AnyPicklerUnpickler.pickle(field, b)
})
builder.endEntry()

}

/** Unpickle with a given unpickler a concrete field. */
private def unpickleAsAny[T](unpickler: Unpickler[T],
reader: PReader, name: String) = {
val field = reader.readField(name)
unpickler.unpickleEntry(field)
}


/** Custom runtime [[Pickler]] and [[Unpickler]] generator of [[Left]]. */
object RuntimeLeftPicklerUnpickler
extends AbstractPicklerUnpickler[Left[Any, Any]] {

val tag = FastTypeTag[Left[Any, Any]]("scala.util.Left[scala.Any,scala.Any]")

def pickle(picklee: Left[Any, Any], builder: PBuilder): Unit =
pickleAsAny(picklee, tag, "a", picklee.a, builder)

def unpickle(tagTpe: String, reader: PReader): Any = {

val tpe = FastTypeTag.apply(tagTpe)
val (leftTpe, _) = twoArgumentTagExtractor[Any, Any](tpe)
Left(unpickleAsAny(getUnpickler(leftTpe, tag), reader, "a"))

}

}

/** Custom runtime [[Pickler]] and [[Unpickler]] generator of [[Right]]. */
object RuntimeRightPicklerUnpickler
extends AbstractPicklerUnpickler[Right[Any, Any]] {

val tag = FastTypeTag[Right[Any, Any]]("scala.util.Right[scala.Any,scala.Any]")

def pickle(picklee: Right[Any, Any], builder: PBuilder): Unit =
pickleAsAny(picklee, tag, "b", picklee.b, builder)

def unpickle(tagTpe: String, reader: PReader): Any = {

val tpe = FastTypeTag.apply(tagTpe)
val (_, rightTpe) = twoArgumentTagExtractor[Any, Any](tpe)
Right(unpickleAsAny(getUnpickler(rightTpe, tag), reader, "b"))

}

}
/** Custom runtime [[Pickler]] and [[Unpickler]] generator of [[Either]]. */
object RuntimeEitherPicklerUnpickler
extends AbstractPicklerUnpickler[Either[Any, Any]] {

val tag = FastTypeTag[Either[Any, Any]]("scala.util.Either[scala.Any,scala.Any]")

def pickle(picklee: Either[Any, Any], builder: PBuilder): Unit = {
picklee match {
case r: Right[Any, Any] =>
RuntimeRightPicklerUnpickler.pickle(r, builder)
case l: Left[Any, Any] =>
RuntimeLeftPicklerUnpickler.pickle(l, builder)
}
}

def unpickle(tag: String, reader: PReader): Any = {
// Tag is expected to be a concrete subclass of Either
AnyPicklerUnpickler.unpickle(tag, reader)
}

}

}

}
Loading

0 comments on commit 3bc60b8

Please sign in to comment.