From 0d64ea397bd39fe8a7d0b4eb5a08e4d8d022302b Mon Sep 17 00:00:00 2001 From: Philipp Haller Date: Tue, 9 Jun 2015 00:45:08 +0200 Subject: [PATCH 1/2] Initial picklers for nullary spores TODO: picklers for nullary spores that capture more than one variable. --- .../spores/NullarySporePicklerImpl.scala | 140 ++++++++++++++++++ .../src/main/scala/scala/spores/Pickler.scala | 11 +- .../scala/spores/run/pickling/Pickling.scala | 24 +++ 3 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 spores-pickling/src/main/scala/scala/spores/NullarySporePicklerImpl.scala diff --git a/spores-pickling/src/main/scala/scala/spores/NullarySporePicklerImpl.scala b/spores-pickling/src/main/scala/scala/spores/NullarySporePicklerImpl.scala new file mode 100644 index 0000000..29d063a --- /dev/null +++ b/spores-pickling/src/main/scala/scala/spores/NullarySporePicklerImpl.scala @@ -0,0 +1,140 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.spores + +import scala.reflect.macros.blackbox.Context + +import scala.pickling._ + +trait NullarySporePicklerImpl { + + def genNullarySporePicklerImpl[R: c.WeakTypeTag](c: Context): c.Tree = { + import c.universe._ + + val rtpe = weakTypeOf[R] + val picklerName = c.freshName(TermName("NullarySporePickler")) + + q""" + object $picklerName extends scala.pickling.Pickler[scala.spores.NullarySpore[$rtpe]] { + def tag = + implicitly[scala.pickling.FastTypeTag[scala.spores.NullarySpore[$rtpe]]] + + def pickle(picklee: scala.spores.NullarySpore[$rtpe], builder: scala.pickling.PBuilder): Unit = { + builder.beginEntry(picklee) + + builder.putField("className", b => { + b.hintTag(scala.pickling.FastTypeTag.String) + b.hintStaticallyElidedType() + scala.pickling.pickler.AllPicklers.stringPickler.pickle(picklee.className, b) + }) + + builder.endEntry() + } + } + $picklerName + """ + } + + def genNullarySporeCSPicklerImpl[R: c.WeakTypeTag, U: c.WeakTypeTag](c: Context)(cPickler: c.Tree): c.Tree = { + import c.universe._ + + val rtpe = weakTypeOf[R] + val utpe = weakTypeOf[U] + + def isEffectivelyPrimitive(tpe: c.Type): Boolean = tpe match { + case TypeRef(_, sym: ClassSymbol, _) if sym.isPrimitive => true + case TypeRef(_, sym, eltpe :: Nil) if sym == definitions.ArrayClass && isEffectivelyPrimitive(eltpe) => true + case _ => false + } + + val sporeTypeName = TypeName("NullarySporeWithEnv") + val picklerName = c.freshName(TermName("NullarySporePickler")) + + q""" + val capturedPickler = $cPickler + object $picklerName extends scala.pickling.Pickler[scala.spores.NullarySporeWithEnv[$rtpe] { type Captured = $utpe }] { + def tag = + implicitly[scala.pickling.FastTypeTag[scala.spores.NullarySporeWithEnv[$rtpe] { type Captured = $utpe }]] + + def pickle(picklee: scala.spores.NullarySporeWithEnv[$rtpe] { type Captured = $utpe }, builder: scala.pickling.PBuilder): Unit = { + // println("[genNullarySporeCSPicklerImpl]") + builder.beginEntry(picklee) + + builder.putField("className", b => { + b.hintTag(scala.pickling.FastTypeTag.String) + b.hintStaticallyElidedType() + scala.pickling.pickler.AllPicklers.stringPickler.pickle(picklee.className, b) + }) + + builder.putField("captured", b => { + b.hintTag(capturedPickler.tag) + ${if (isEffectivelyPrimitive(utpe)) q"b.hintStaticallyElidedType()" else q""} + capturedPickler.pickle(picklee.asInstanceOf[$sporeTypeName[$rtpe]].captured.asInstanceOf[$utpe], b) + }) + + builder.endEntry() + } + } + $picklerName + """ + } + + def genNullarySporeCSUnpicklerImpl[R: c.WeakTypeTag](c: Context): c.Tree = { + import c.universe._ + + val rtpe = weakTypeOf[R] + val unpicklerName = c.freshName(TermName("NullarySporeUnpickler")) + val utils = new PicklerUtils[c.type](c) + val reader = TermName("reader") + val readClassName = utils.readClassNameTree(reader) + + q""" + object $unpicklerName extends scala.pickling.Unpickler[scala.spores.NullarySpore[$rtpe]] { + def tag = + implicitly[scala.pickling.FastTypeTag[scala.spores.NullarySpore[$rtpe]]] + + def unpickle(tag: String, $reader: scala.pickling.PReader): Any = { + val result = $readClassName + + // println("[genNullarySporeCSUnpicklerImpl] creating instance of class " + result) + val clazz = java.lang.Class.forName(result) + val sporeInst = (try clazz.newInstance() catch { + case t: Throwable => + val inst = scala.concurrent.util.Unsafe.instance.allocateInstance(clazz) + val privateClassNameField = clazz.getDeclaredField("_className") + privateClassNameField.setAccessible(true) + privateClassNameField.set(inst, result) + inst + }).asInstanceOf[scala.spores.NullarySpore[$rtpe]] + + if (sporeInst.isInstanceOf[scala.spores.NullarySporeWithEnv[$rtpe]]) { + // println("[genNullarySporeCSUnpicklerImpl] spore class is NullarySporeWithEnv") + val sporeWithEnvInst = sporeInst.asInstanceOf[scala.spores.NullarySporeWithEnv[$rtpe]] + val reader3 = $reader.readField("captured") + val tag3 = reader3.beginEntry() + val value = { + if (reader3.atPrimitive) { + reader3.readPrimitive() + } else { + val unpickler3 = scala.pickling.runtime.RuntimeUnpicklerLookup.genUnpickler(scala.reflect.runtime.currentMirror, tag3) + unpickler3.unpickle(tag3, reader3) + } + } + reader3.endEntry() + sporeWithEnvInst.captured = value.asInstanceOf[sporeWithEnvInst.Captured] + } + + sporeInst + } + } + $unpicklerName + """ + } + +} diff --git a/spores-pickling/src/main/scala/scala/spores/Pickler.scala b/spores-pickling/src/main/scala/scala/spores/Pickler.scala index 7ab5a1a..c8bc08e 100644 --- a/spores-pickling/src/main/scala/scala/spores/Pickler.scala +++ b/spores-pickling/src/main/scala/scala/spores/Pickler.scala @@ -14,7 +14,7 @@ import scala.reflect.macros.blackbox.Context import scala.pickling._ -object SporePickler extends SimpleSporePicklerImpl { +object SporePickler extends SimpleSporePicklerImpl with NullarySporePicklerImpl { /*implicit*/ def genSporePickler[T, R, U](implicit cPickler: Pickler[U], cUnpickler: Unpickler[U]) : Pickler[Spore[T, R] { type Captured = U }] with Unpickler[Spore[T, R] { type Captured = U }] = macro genSporePicklerImpl[T, R, U] @@ -118,6 +118,12 @@ object SporePickler extends SimpleSporePicklerImpl { implicit def genSimpleSpore3Pickler[T1, T2, T3, R]: Pickler[Spore3[T1, T2, T3, R]] = macro genSimpleSpore3PicklerImpl[T1, T2, T3, R] + implicit def genNullarySporePickler[R]: Pickler[NullarySpore[R]] = + macro genNullarySporePicklerImpl[R] + + implicit def genNullarySporeCSPickler[R, U](implicit cPickler: Pickler[U]): Pickler[NullarySporeWithEnv[R] { type Captured = U }] = + macro genNullarySporeCSPicklerImpl[R, U] + // type `U` Date: Tue, 9 Jun 2015 09:18:23 +0200 Subject: [PATCH 2/2] Add test with nullary spore and case class --- .../scala/spores/run/pickling/Pickling.scala | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/spores-pickling/src/test/scala/scala/spores/run/pickling/Pickling.scala b/spores-pickling/src/test/scala/scala/spores/run/pickling/Pickling.scala index f2f7cda..80dff2a 100644 --- a/spores-pickling/src/test/scala/scala/spores/run/pickling/Pickling.scala +++ b/spores-pickling/src/test/scala/scala/spores/run/pickling/Pickling.scala @@ -6,12 +6,51 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import scala.concurrent.Future + import scala.pickling._ -import Defaults._ -import json._ +import scala.pickling.Defaults._ +import scala.pickling.json._ import SporePickler._ + +class LocalSilo[U, T <: Traversable[U]](val value: T) { + def send(): Future[T] = + Future.successful(value) +} + +case class InitSiloFun[U, T <: Traversable[U]](fun: NullarySpore[LocalSilo[U, T]], refId: Int) + + +object GlobalFuns { + private val summary = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis +nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore +eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt +in culpa qui officia deserunt mollit anim id est laborum.""" + + private lazy val words = summary.replace('\n', ' ').split(" ") + + def randomWord(random: scala.util.Random): String = { + val index = random.nextInt(words.length) + words(index) + } + + def populateSilo(numLines: Int, random: scala.util.Random): LocalSilo[String, List[String]] = { + // each string is a concatenation of 10 random words, separated by space + val buffer = collection.mutable.ListBuffer[String]() + val lines = for (i <- 0 until numLines) yield { + val tenWords = for (_ <- 1 to 10) yield randomWord(random) + buffer += tenWords.mkString(" ") + } + new LocalSilo(buffer.toList) + } +} + + @RunWith(classOf[JUnit4]) class PicklingSpec { @Test @@ -202,4 +241,19 @@ class PicklingSpec { } doPickle(s) } + + @Test + def `pickle case class with nullary spore`(): Unit = { + val s: NullarySpore[LocalSilo[String, List[String]]] = spore { + delayed { + GlobalFuns.populateSilo(10, new scala.util.Random(100)) + } + } + + val init = InitSiloFun(s, 5) + val p = init.pickle + val up = p.unpickle[InitSiloFun[String, List[String]]] + assert(up.refId == 5) + assert(up.fun().value.size == 10) + } }