From 3bc60b819339f2137e015c5d05dd8130f481ca58 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 29 Apr 2016 16:25:17 +0200 Subject: [PATCH] Add custom runtime generators for Either * 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. --- .../scala/scala/pickling/PicklingErrors.scala | 12 +- .../internal/DefaultPicklerRegistry.scala | 15 +- .../internal/RuntimePicklerRegistry.scala | 3 +- .../scala/scala/pickling/pickler/Any.scala | 34 ++-- .../scala/scala/pickling/pickler/Either.scala | 187 ++++++++++++++---- .../pickling/pickler/GeneratorRegistry.scala | 71 +++++-- .../pickling/runtime/CustomRuntime.scala | 70 ++----- .../pickling/pickler/EitherPicklerTest.scala | 5 + .../scala/pickling/PicklingProtocol.scala | 20 ++ .../picklers/EitherPicklersTest.scala | 35 ++++ .../registry/SwitchRuntimeRegistryInit.scala | 16 +- 11 files changed, 327 insertions(+), 141 deletions(-) create mode 100644 sandbox-test/src/test/scala/scala/pickling/PicklingProtocol.scala create mode 100644 sandbox-test/src/test/scala/scala/pickling/picklers/EitherPicklersTest.scala diff --git a/core/src/main/scala/scala/pickling/PicklingErrors.scala b/core/src/main/scala/scala/pickling/PicklingErrors.scala index 92fb56ba5f..9fd4f2ccdc 100644 --- a/core/src/main/scala/scala/pickling/PicklingErrors.scala +++ b/core/src/main/scala/scala/pickling/PicklingErrors.scala @@ -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 { @@ -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. diff --git a/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala b/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala index baad7c7703..9ac78969df 100644 --- a/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala +++ b/core/src/main/scala/scala/pickling/internal/DefaultPicklerRegistry.scala @@ -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() @@ -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]]) } diff --git a/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala b/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala index d6b846edfc..36f7aeb0c1 100644 --- a/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala +++ b/core/src/main/scala/scala/pickling/internal/RuntimePicklerRegistry.scala @@ -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) diff --git a/core/src/main/scala/scala/pickling/pickler/Any.scala b/core/src/main/scala/scala/pickling/pickler/Any.scala index 0d82eef507..446427d40f 100644 --- a/core/src/main/scala/scala/pickling/pickler/Any.scala +++ b/core/src/main/scala/scala/pickling/pickler/Any.scala @@ -12,6 +12,8 @@ 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 @@ -19,25 +21,35 @@ object AnyPicklerUnpickler extends AbstractPicklerUnpickler[Any] * 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" diff --git a/core/src/main/scala/scala/pickling/pickler/Either.scala b/core/src/main/scala/scala/pickling/pickler/Either.scala index d7fd1c4985..dd0ccd0cd4 100644 --- a/core/src/main/scala/scala/pickling/pickler/Either.scala +++ b/core/src/main/scala/scala/pickling/pickler/Either.scala @@ -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) + } + + } + +} -} \ No newline at end of file diff --git a/core/src/main/scala/scala/pickling/pickler/GeneratorRegistry.scala b/core/src/main/scala/scala/pickling/pickler/GeneratorRegistry.scala index 6c89e5b38f..1ca1e9ee59 100644 --- a/core/src/main/scala/scala/pickling/pickler/GeneratorRegistry.scala +++ b/core/src/main/scala/scala/pickling/pickler/GeneratorRegistry.scala @@ -1,29 +1,35 @@ package scala.pickling.pickler -import scala.pickling.PicklingErrors.{FailedUnpicklerGeneration, FailedPicklerGeneration} +import scala.pickling.PicklingErrors.{TypeMismatch, FailedPicklerGeneration} +import scala.pickling.{Pickler, Unpickler} import scala.pickling.spi.PicklerRegistry import scala.pickling.tags.FastTypeTag /** Register some generators at boot time so that runtime generation can - * reuse them and not try to reflectively know what to do to generate - * the picklers and unpicklers. + * reuse them and not try to reflectively figure out the structure of + * classes to create picklers and unpicklers accordingly. * * This trait has no relation with the generation of pickler/unpicklers, * although they share the same denotation. - * - * NOTE: We may want to change these names in future releases for the sake - * of clarity. */ trait GeneratorRegistry { import PicklerRegistry._ import scala.pickling.internal.currentRuntime + def registerPicklerAsGen[T](pu: Pickler[T] with Unpickler[T]) = + currentRuntime.picklers.registerPicklerUnpicklerGenerator( + pu.tag.typeConstructor, _ => pu + ) + def registerGen[T](tag: String, gen: PicklerUnpicklerGen[T]) = currentRuntime.picklers.registerPicklerUnpicklerGenerator(tag, gen) } +/** Mix-in and make use of generation-related code that runtime + * picklers usually need for unknown type parameters as [[Any]]. + */ trait GeneratorHelper { import scala.language.higherKinds @@ -32,29 +38,56 @@ trait GeneratorHelper { type FastTypeTagSpecializer[T] = FastTypeTag[_] => FastTypeTag[T] + type GenPicklerSignature[PU[_]] = + (ClassLoader, Class[_], FastTypeTag[_]) => PU[_] + + /** Generic get that abstracts over [[Pickler]] and [[Unpickler]]. */ + def get[PU[_], S](query: String => Option[PU[_]], + key: String, error: => Throwable): PU[S] = { + if(key == FastTypeTag.Any.key) + AnyPicklerUnpickler.asInstanceOf[PU[S]] + else query(key).getOrElse(throw error).asInstanceOf[PU[S]] + } + + + /** Get a pickler from the registry or throw exception otherwise. */ + def getPickler[T, S](tpe: FastTypeTag[T], fullTpe: FastTypeTag[S]) = + get[Pickler, T](currentRuntime.picklers.lookupPickler, tpe.key, + new FailedPicklerGeneration(fullTpe.toString, tpe.toString)) + + /** Get a unpickler from the registry or throw exception otherwise. */ + def getUnpickler[T, S](tpe: FastTypeTag[T], fullTpe: FastTypeTag[S]) = + get[Unpickler, T](currentRuntime.picklers.lookupUnpickler, tpe.key, + new FailedPicklerGeneration(fullTpe.toString, tpe.toString)) + /** Creates a pickling generator that can be registered at runtime. */ def generateHelper[T](elementTagExtractor: FastTypeTagSpecializer[T]) (tpe: FastTypeTag[_]): (Pickler[T], Unpickler[T]) = { val elementType = elementTagExtractor(tpe) - val elementKey = elementType.key - - def get[PU[_], S](from: String => Option[PU[_]], error: => Throwable): PU[S] = { - if(elementKey == FastTypeTag.Any.key) - AnyPicklerUnpickler.asInstanceOf[PU[S]] - else from(elementKey).getOrElse(throw error).asInstanceOf[PU[S]] - } - - val elemPickler = get[Pickler, T](currentRuntime.picklers.lookupPickler, - new FailedPicklerGeneration(tpe.toString, elementType.toString)) - val elemUnpickler = get[Unpickler, T](currentRuntime.picklers.lookupUnpickler, - new FailedUnpicklerGeneration(tpe.toString, elementType.toString)) + getPickler(elementType, tpe) -> getUnpickler(elementType, tpe) - (elemPickler, elemUnpickler) } /** Specialize a [[FastTypeTag]] for a type [[T]]. */ def specialize[T](tag: FastTypeTag[_]): FastTypeTag[T] = tag.asInstanceOf[FastTypeTag[T]] + /** Extract two type parameters from a type constructor and + * cast them to some concrete types [[T]] and [[S]]. + * + * @tparam T First type we want to convert to + * @tparam S Second type we want to convert to + * + * @return A tuple of tags of (T, S) + */ + def twoArgumentTagExtractor[T, S](tpe: FastTypeTag[_]): (FastTypeTag[T], FastTypeTag[S]) = { + val typeArgs = tpe.typeArgs + typeArgs match { + case List(one, two) => + one.asInstanceOf[FastTypeTag[T]] -> two.asInstanceOf[FastTypeTag[S]] + case _ => throw TypeMismatch(List(tpe), typeArgs) + } + } + } diff --git a/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala b/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala index 9b0266ddbd..ceca94ace2 100644 --- a/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala +++ b/core/src/main/scala/scala/pickling/runtime/CustomRuntime.scala @@ -118,64 +118,35 @@ trait CustomRuntime { } /** Runtime [[Pickler]] and [[Unpickler]] of tuple with unknown types. */ - class Tuple2RuntimePicklerUnpickler extends AbstractPicklerUnpickler[(Any, Any)] { + object Tuple2RuntimePicklerUnpickler extends AbstractPicklerUnpickler[(Any, Any)] { val tag = FastTypeTag[(Any, Any)]("scala.Tuple2[scala.Any, scala.Any]") - def pickleField(name: String, value: Any, builder: PBuilder): Unit = { - // TODO This pickler should use the known tag if it is passed. - val (tag1, pickler1) = if (value == null) { - (FastTypeTag.Null.asInstanceOf[FastTypeTag[Any]], - Defaults.nullPickler.asInstanceOf[Pickler[Any]]) - } else { - val clazz = value.getClass - val tag = FastTypeTag.makeRaw(clazz) - val pickler = currentRuntime.picklers - .genPickler(clazz.getClassLoader, clazz, tag) - .asInstanceOf[Pickler[Any]] - (tag, pickler) - } - - builder.putField(name, b => { - pickler1.pickle(value, b) - }) - } - def pickle(picklee: (Any, Any), builder: PBuilder): Unit = { builder.beginEntry(picklee, tag) - val fld1 = picklee._1 - pickleField("_1", fld1, builder) - val fld2 = picklee._2 - pickleField("_2", fld2, builder) + builder.putField("_1", b => { + AnyPicklerUnpickler.pickle(picklee._1, b) + }) + + builder.putField("_2", b => { + AnyPicklerUnpickler.pickle(picklee._2, b) + }) builder.endEntry() } def unpickleField(name: String, reader: PReader): Any = { - val reader1 = reader.readField(name) - val tag1 = reader1.beginEntry() - - val value = { - if (reader1.atPrimitive) { - reader1.readPrimitive() - } else { - val unpickler1 = currentRuntime.picklers - .genUnpickler(reflectRuntime.currentMirror, tag1) - try { - unpickler1.unpickle(tag1, reader1) - } catch { - case e@BasePicklingException(msg, cause) => - throw Wrapper(e, - s"""Error in unpickle of '${this.getClass.getName}': - |Field name: '$name' - |Field tag: '$tag1' - |Message:""".stripMargin) - } - } + try { + val reader1 = reader.readField(name) + AnyPicklerUnpickler.unpickleEntry(reader1) + } catch { + case e @ BasePicklingException(msg, cause) => + throw Wrapper(e, + s"""Error in unpickle of '${this.getClass.getName}': + |Field name: '$name' + |Message:""".stripMargin) } - reader1.endEntry() - value } def unpickle(tag: String, reader: PReader): Any = { @@ -188,12 +159,14 @@ trait CustomRuntime { val tuplePicklerGenerator: PicklerUnpicklerGen[(Any, Any)] = { tpe => // TODO - Actually extract the tpe of the internal things. val tag = FastTypeTag.apply(tpe.toString) - new Tuple2RuntimePicklerUnpickler + // TODO Remove this redundancy and reuse the tag above + Tuple2RuntimePicklerUnpickler } val tupleUnpicklerGenerator: UnpicklerGen[(Any,Any)] = { case FastTypeTag(_, List(left, right)) => + // Lookup the type params since they are explicit when unpickling val lhs = currentRuntime.picklers.lookupUnpickler(left.toString) .getOrElse(AnyPicklerUnpickler).asInstanceOf[Unpickler[Any]] val rhs = currentRuntime.picklers.lookupUnpickler(right.toString) @@ -201,8 +174,7 @@ trait CustomRuntime { new Tuple2RuntimeKnownTagUnpickler(lhs, rhs) - case tpe => new Tuple2RuntimePicklerUnpickler + case tpe => Tuple2RuntimePicklerUnpickler } } - diff --git a/core/src/test/scala/scala/pickling/pickler/EitherPicklerTest.scala b/core/src/test/scala/scala/pickling/pickler/EitherPicklerTest.scala index 38f9588063..8869bdd7b9 100644 --- a/core/src/test/scala/scala/pickling/pickler/EitherPicklerTest.scala +++ b/core/src/test/scala/scala/pickling/pickler/EitherPicklerTest.scala @@ -2,6 +2,8 @@ package scala.pickling.pickler import org.scalatest.FunSuite +import scala.pickling.internal.HybridRuntime + /** * Tests Either picklers */ @@ -9,6 +11,7 @@ class EitherPicklerTest extends FunSuite { import scala.pickling._, Defaults._, static._, json._ test("pickle Left") { val l: Left[Int, String] = Left(1) + val m = AnyPicklerUnpickler.pickle(l,pickleFormat.createBuilder()) val up = l.pickle.unpickle[Left[Int,String]] assert(l == up) @@ -30,4 +33,6 @@ class EitherPicklerTest extends FunSuite { val up22 = r2.pickle.unpickle[Either[Int,String]] assert(r2 == up22) } + + } diff --git a/sandbox-test/src/test/scala/scala/pickling/PicklingProtocol.scala b/sandbox-test/src/test/scala/scala/pickling/PicklingProtocol.scala new file mode 100644 index 0000000000..a01534ff17 --- /dev/null +++ b/sandbox-test/src/test/scala/scala/pickling/PicklingProtocol.scala @@ -0,0 +1,20 @@ +package scala.pickling + +import scala.pickling.binary.BinaryFormats +import scala.pickling.internal.HybridRuntime +import scala.pickling.json.JsonFormats +import scala.pickling.pickler.AllPicklers + +trait PicklingProtocol extends { + val oldRuntime = internal.currentRuntime + val onlyLookup = { + val currentRuntime = + if(oldRuntime.isInstanceOf[HybridRuntime]) oldRuntime + else new HybridRuntime + internal.replaceRuntime(currentRuntime) + } +} with Ops with AllPicklers + +object JsonPicklingProtocol extends PicklingProtocol with JsonFormats + +object BinaryPicklingProtocol extends PicklingProtocol with BinaryFormats diff --git a/sandbox-test/src/test/scala/scala/pickling/picklers/EitherPicklersTest.scala b/sandbox-test/src/test/scala/scala/pickling/picklers/EitherPicklersTest.scala new file mode 100644 index 0000000000..461a64920e --- /dev/null +++ b/sandbox-test/src/test/scala/scala/pickling/picklers/EitherPicklersTest.scala @@ -0,0 +1,35 @@ +package scala.pickling.picklers + +import org.scalatest.FunSuite + +import scala.pickling.JsonPicklingProtocol + +class EitherPicklersTest extends FunSuite { + + import JsonPicklingProtocol._ + + test("pickle `left` when type is unknown") { + val l: Any = Left[Int, String](1) + val up = l.pickle.unpickle[Any] + assert(l == up) + } + + test("pickle `right` when type is unknown") { + val r: Any = Right[Int, String]("hello") + val up = r.pickle.unpickle[Any] + assert(r == up) + } + + test("pickle `either` when type is unknown") { + + val l: Any = Left[Int, String](1) + val up1 = l.pickle.unpickle[Any] + assert(l == up1) + + val r: Any = Right[Int, String]("hello") + val up2 = r.pickle.unpickle[Any] + assert(r == up2) + + } + +} diff --git a/sandbox-test/src/test/scala/scala/pickling/registry/SwitchRuntimeRegistryInit.scala b/sandbox-test/src/test/scala/scala/pickling/registry/SwitchRuntimeRegistryInit.scala index be5b8f36f5..298cd34e38 100644 --- a/sandbox-test/src/test/scala/scala/pickling/registry/SwitchRuntimeRegistryInit.scala +++ b/sandbox-test/src/test/scala/scala/pickling/registry/SwitchRuntimeRegistryInit.scala @@ -7,18 +7,14 @@ import scala.pickling.internal.HybridRuntime import scala.pickling.json.JsonFormats import scala.pickling.pickler.AllPicklers -object Protocol extends { - val oldRuntime = internal.currentRuntime - val currentRuntime = new HybridRuntime - val onlyLookup = internal.replaceRuntime(currentRuntime) -} with Ops with AllPicklers with JsonFormats - class SwitchRuntimeRegistryInit extends FunSuite { - import Protocol._ + import JsonPicklingProtocol._ test("registry should be initialized when switching runtime strategies") { + import scala.pickling.internal.currentRuntime + case class Foo(i: Int) val pf = implicitly[AbstractPicklerUnpickler[Foo]] @@ -26,18 +22,12 @@ class SwitchRuntimeRegistryInit extends FunSuite { // the registry again. If it fails it does. implicitly[Pickler[List[String]]] - try { val lookup = currentRuntime.picklers.lookupPickler(pf.tag.key) assert(lookup !== None) val pf2 = implicitly[AbstractPicklerUnpickler[Foo]] val lookup2 = currentRuntime.picklers.lookupPickler(pf.tag.key) assert(lookup === lookup2) - } finally { - internal.replaceRuntime(oldRuntime) - // Just in case it doesn't get init - implicitly[Pickler[List[String]]] - } }