From 00a198d46e9f8e2957ad5279ec638631a7666013 Mon Sep 17 00:00:00 2001 From: jvican Date: Sun, 15 May 2016 01:54:38 +0200 Subject: [PATCH 1/2] Add tests for Tuple2 (normal and specialized) Now, we support pickling and unpickling specialized tuples. It's necessary to add a special method to check class equality since the specialized classes are implemented as subclasses and therefore have its own class name, failing a simple class equality. --- .../generation/pickler/Tuple2Pickler.scala | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 core/src/test/scala/scala/pickling/generation/pickler/Tuple2Pickler.scala diff --git a/core/src/test/scala/scala/pickling/generation/pickler/Tuple2Pickler.scala b/core/src/test/scala/scala/pickling/generation/pickler/Tuple2Pickler.scala new file mode 100644 index 0000000000..b0f672cf64 --- /dev/null +++ b/core/src/test/scala/scala/pickling/generation/pickler/Tuple2Pickler.scala @@ -0,0 +1,28 @@ +package scala.pickling.generation.pickler + +import org.scalatest.FunSuite + +import scala.pickling._ +import Defaults._ +import json._ +import scala.pickling.util.ClassMapper +import static._ + +class Tuple2Pickler extends FunSuite { + + test("pickle/unpickle (Int, String)") { + val t = (1, "") + val t2 = t.pickle.unpickle[(Int, String)] + assert(t === t2) + } + + test("pickle/unpickle any specialized tuple like (Int, Int)") { + val t = (1, 2) + val clz = classOf[(Int, Int)] + val clzT = t.getClass + println(ClassMapper.areSameClasses(clzT, clz)) + val t2 = t.pickle.unpickle[(Int, Int)] + assert(t === t2) + } + +} From caf87300083b013e3a66c9f07b5d913e2ba26a0f Mon Sep 17 00:00:00 2001 From: jvican Date: Sun, 15 May 2016 01:56:50 +0200 Subject: [PATCH 2/2] Add extendible way to pickle specialized classes * Reuse code in `RuntimePicklerRegistry` and `sourcegen`. * Create `ClassMapper` to delegate the tasks of checking class equality. --- .../scala/pickling/generator/sourcegen.scala | 19 ++++-- .../internal/DefaultPicklerRegistry.scala | 16 ++++- .../internal/RuntimePicklerRegistry.scala | 61 ------------------- .../pickling/runtime/CustomRuntime.scala | 4 +- .../scala/pickling/spi/PicklerRegistry.scala | 11 ++++ .../scala/pickling/util/ClassMapper.scala | 33 ++++++++++ 6 files changed, 72 insertions(+), 72 deletions(-) delete mode 100644 core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala create mode 100644 core/src/main/scala/scala/pickling/util/ClassMapper.scala diff --git a/core/src/main/scala/scala/pickling/generator/sourcegen.scala b/core/src/main/scala/scala/pickling/generator/sourcegen.scala index 4047727119..bd601feb2c 100644 --- a/core/src/main/scala/scala/pickling/generator/sourcegen.scala +++ b/core/src/main/scala/scala/pickling/generator/sourcegen.scala @@ -70,15 +70,21 @@ private[pickling] trait SourceGenerator extends Macro with tags.FastTypeTagMacro } } + val classMapper = q"$picklingPath.util.ClassMapper" val unrecognizedClass = q"$errorsPath.UnrecognizedClass" def genSubclassDispatch(x: SubclassDispatch): c.Tree = { val tpe = x.parent.tpe[c.universe.type](c.universe) - val clazzName = newTermName("clazz") - val compileTimeDispatch: List[CaseDef] = (x.subClasses map { subtpe => - val tpe = subtpe.tpe[c.universe.type](c.universe) - CaseDef(Bind(clazzName, Ident(nme.WILDCARD)), q"clazz == classOf[$tpe]", createPickler(tpe, q"builder")) - })(collection.breakOut) + val clazz = newTermName("clazz") + val compileTimeDispatch: List[CaseDef] = + (x.subClasses map { subtpe => + val tpe = subtpe.tpe[c.universe.type](c.universe) + CaseDef( + Bind(clazz, Ident(nme.WILDCARD)), + q"_root_.scala.pickling.util.ClassMapper.areSameClasses($clazz, classOf[$tpe])", + createPickler(tpe, q"builder") + ) + })(collection.breakOut) val failDispatch = { val dispatcheeNames = x.subClasses.map(_.className).mkString(", ") @@ -111,7 +117,8 @@ private[pickling] trait SourceGenerator extends Macro with tags.FastTypeTagMacro case Some(b) => val parentTpe = x.parent.tpe[c.universe.type](c.universe) val impl = generatePickleImplFromAst(b) - q"""if(classOf[$parentTpe] == picklee.getClass) $impl else $subclasses""" + val checkIfParent = q"$classMapper.areSameClasses(picklee.getClass, classOf[$parentTpe])" + q"""if($checkIfParent) $impl else $subclasses""" } } def genPickleEntry(op: PickleEntry): c.Tree = { diff --git a/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala b/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala index 9ac78969df..7323d1267e 100644 --- a/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala +++ b/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala @@ -4,13 +4,14 @@ import scala.collection.concurrent.TrieMap import scala.collection.mutable import scala.pickling.spi.PicklerRegistry._ import scala.reflect.runtime.universe.Mirror - import scala.pickling._ +import scala.pickling.runtime.CustomRuntime import scala.pickling.spi.{PicklerRegistry, RuntimePicklerGenerator} +import scala.pickling.util.ClassMapper /** Default pickle registry just uses TrieMaps and delegates behavior to a runtime pickler generator. */ final class DefaultPicklerRegistry(generator: RuntimePicklerGenerator) - extends PicklerRegistry with RuntimePicklerRegistry { + extends PicklerRegistry with CustomRuntime { type PicklerGenerator = FastTypeTag[_] => Pickler[_] type UnpicklerGenerator = FastTypeTag[_] => Unpickler[_] @@ -20,7 +21,16 @@ final class DefaultPicklerRegistry(generator: RuntimePicklerGenerator) private val unpicklerMap: mutable.Map[String, Unpickler[_]] = new TrieMap[String, Unpickler[_]] private val unpicklerGenMap: mutable.Map[String, UnpicklerGenerator] = new TrieMap[String, UnpicklerGenerator] - registerRuntimePicklersAtInit() + val tupleGenerators: (PicklerGen[Any], UnpicklerGen[Any]) = + (tuplePicklerGenerator.asInstanceOf[PicklerGen[Any]], + tupleUnpicklerGenerator.asInstanceOf[UnpicklerGen[Any]]) + + val templatesToRegister = + Vector("scala.Tuple2" -> tupleGenerators) ++ + ClassMapper.specializedTupleNamesFor("scala.Tuple2") + .map(_ -> tupleGenerators) + + registerTemplatesAtInit(templatesToRegister) override def genUnpickler(mirror: Mirror, tagKey: String)(implicit share: refs.Share): Unpickler[_] = { lookupUnpickler(tagKey) match { diff --git a/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala b/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala deleted file mode 100644 index 36f7aeb0c1..0000000000 --- a/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala +++ /dev/null @@ -1,61 +0,0 @@ -package scala.pickling -package internal - -import scala.pickling.runtime.CustomRuntime -import scala.pickling.spi.PicklerRegistry - -/** Helper class to register picklers and unpicklers that - * handle special/crazy classes for runtime generation. - * - * Related to [[scala.pickling.runtime.CustomRuntime]]. - * Note: Currently this only handles Tuple2s. - */ -trait RuntimePicklerRegistry extends CustomRuntime { - this: PicklerRegistry => - - val tupleGenerators = (tuplePicklerGenerator, tupleUnpicklerGenerator) - - /* Don't need to add methods to add/remove from - * this list because this is meant to be used - * internally. If a user wants to do the same, - * she just needs to use the [[PicklerRegistry]]. */ - val picklersToRegister = List( - ("scala.Tuple2", tupleGenerators), - ("scala.Tuple2$mcII$sp", tupleGenerators), - ("scala.Tuple2$mcIJ$sp", tupleGenerators), - ("scala.Tuple2$mcID$sp", tupleGenerators), - ("scala.Tuple2$mcIZ$sp", tupleGenerators), - - ("scala.Tuple2$mcJI$sp", tupleGenerators), - ("scala.Tuple2$mcJJ$sp", tupleGenerators), - ("scala.Tuple2$mcJD$sp", tupleGenerators), - ("scala.Tuple2$mcJC$sp", tupleGenerators), - ("scala.Tuple2$mcJZ$sp", tupleGenerators), - - ("scala.Tuple2$mcDI$sp", tupleGenerators), - ("scala.Tuple2$mcDJ$sp", tupleGenerators), - ("scala.Tuple2$mcDD$sp", tupleGenerators), - ("scala.Tuple2$mcDC$sp", tupleGenerators), - ("scala.Tuple2$mcDZ$sp", tupleGenerators), - - ("scala.Tuple2$mcCI$sp", tupleGenerators), - ("scala.Tuple2$mcCJ$sp", tupleGenerators), - ("scala.Tuple2$mcCD$sp", tupleGenerators), - ("scala.Tuple2$mcCC$sp", tupleGenerators), - ("scala.Tuple2$mcCZ$sp", tupleGenerators), - - ("scala.Tuple2$mcZI$sp", tupleGenerators), - ("scala.Tuple2$mcZJ$sp", tupleGenerators), - ("scala.Tuple2$mcZD$sp", tupleGenerators), - ("scala.Tuple2$mcZC$sp", tupleGenerators), - ("scala.Tuple2$mcZZ$sp", tupleGenerators) - ) - - final def registerRuntimePicklersAtInit(): Unit = { - for((key, (pickler, unpickler)) <- picklersToRegister) { - registerPicklerGenerator(key, pickler) - registerUnpicklerGenerator(key, unpickler) - } - } - -} diff --git a/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala b/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala index ceca94ace2..b2c5128900 100644 --- a/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala +++ b/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala @@ -156,11 +156,11 @@ trait CustomRuntime { } } - val tuplePicklerGenerator: PicklerUnpicklerGen[(Any, Any)] = { tpe => + val tuplePicklerGenerator: PicklerGen[(Any, Any)] = { tpe => // TODO - Actually extract the tpe of the internal things. val tag = FastTypeTag.apply(tpe.toString) // TODO Remove this redundancy and reuse the tag above - Tuple2RuntimePicklerUnpickler + Tuple2RuntimePicklerUnpickler.asInstanceOf[Pickler[(Any, Any)]] } val tupleUnpicklerGenerator: UnpicklerGen[(Any,Any)] = { diff --git a/core/src/main/scala/scala/pickling/spi/PicklerRegistry.scala b/core/src/main/scala/scala/pickling/spi/PicklerRegistry.scala index 8616ecf8a1..17a65e72f8 100644 --- a/core/src/main/scala/scala/pickling/spi/PicklerRegistry.scala +++ b/core/src/main/scala/scala/pickling/spi/PicklerRegistry.scala @@ -125,6 +125,17 @@ trait PicklerRegistry { */ private[pickling] def dumpStateTo(r: PicklerRegistry): Unit + type Templates = (PicklerGen[Any], UnpicklerGen[Any]) + + /** Register templates (also known as generators) that know how to pickle + * and unpickle types at runtime without using reflection.*/ + final def registerTemplatesAtInit(ts: Vector[(String, Templates)]): Unit = { + for((key, (pickler, unpickler)) <- ts) { + registerPicklerGenerator(key, pickler) + registerUnpicklerGenerator(key, unpickler) + } + } + } object PicklerRegistry { diff --git a/core/src/main/scala/scala/pickling/util/ClassMapper.scala b/core/src/main/scala/scala/pickling/util/ClassMapper.scala new file mode 100644 index 0000000000..2cdf20dc68 --- /dev/null +++ b/core/src/main/scala/scala/pickling/util/ClassMapper.scala @@ -0,0 +1,33 @@ +package scala.pickling.util + +object ClassMapper { + + val specializedTuplesTemplate = + Vector( + "$mcII$sp", "$mcIJ$sp", "$mcID$sp", "$mcIZ$sp", "$mcJI$sp", "$mcJJ$sp", + "$mcJD$sp", "$mcJC$sp", "$mcJZ$sp", "$mcDI$sp", "$mcDJ$sp", "$mcDD$sp", + "$mcDC$sp", "$mcDZ$sp", "$mcCI$sp", "$mcCJ$sp", "$mcCD$sp", "$mcCC$sp", + "$mcCZ$sp", "$mcZI$sp", "$mcZJ$sp", "$mcZD$sp", "$mcZC$sp", "$mcZZ$sp" + ) + + /* Map specialized classes to classes. Canonical use case: tuples. + * We map classes instead of strings to check at runtime that they exist. */ + val specialMappingClasses: Map[Class[_], Class[_]] = + mapSpecializedTuplesFor("scala.Tuple2") // add also other special cases + + def specializedTupleNamesFor(tupleClassName: String): Vector[String] = + specializedTuplesTemplate.map(tupleClassName + _) + + def mapSpecializedTuplesFor(tupleClassName: String): Map[Class[_], Class[_]] = { + val tupleClass = Class.forName(tupleClassName) + specializedTupleNamesFor(tupleClassName) + .map(Class.forName).map(_ -> tupleClass).toMap + } + + @inline def isSpecializedClass(specialized: Class[_], clazz: Class[_]) = + specialMappingClasses.get(specialized).exists(_ == clazz) + + def areSameClasses(clazz: Class[_], clazzT: Class[_]): Boolean = + clazz == clazzT || isSpecializedClass(clazz, clazzT) + +}