diff --git a/README b/README index 59a6cb1..0f6c99c 100644 --- a/README +++ b/README @@ -4,8 +4,7 @@ jacks - Jackson module for Scala types, including mutable & immutable collections, Option, Tuple, Symbol, and case classes. - This version of jacks has been tested against Scala 2.11.0, 2.10.4 and - Jackson 2.3.3 + This version of jacks has been tested against Scala 2.11.1, and Jackson 2.3.3 Join the lambdaWorks-OSS Google Group to discuss this project: @@ -24,14 +23,14 @@ Basic Usage The preconfigured ObjectMapper is available via JacksMapper.mapper and a new ScalaModule may be added to any existing ObjectMapper. -Erasure & Manifests +Erasure & TypeTag Reading parameterized types requires a Jackson JavaType which can be - derived from a Scala Manifest. The readValue methods of JacksMapper take - an implicit Manifest and look up the appropriate JavaType. + derived from a Scala TypeTag. The readValue methods of JacksMapper take + an implicit TypeTag and look up the appropriate JavaType. - JacksMapper.resolve(manifest) will also return the correct JavaType - given a Manifest. + JacksMapper.resolve(typeTag) will also return the correct JavaType + given a TypeTag. Serialization Details diff --git a/project/build.scala b/project/build.scala index ae5c9f0..42c958e 100644 --- a/project/build.scala +++ b/project/build.scala @@ -8,9 +8,9 @@ object JacksBuild extends Build { organization := "com.lambdaworks", scalaVersion := "2.11.0", - crossScalaVersions := Seq("2.11.0", "2.10.4"), + crossScalaVersions := Seq("2.11.0"), - libraryDependencies <+= scalaVersion("org.scala-lang" % "scalap" % _), + libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _), libraryDependencies ++= Seq( "com.fasterxml.jackson.core" % "jackson-databind" % "2.3.3", "org.scalatest" %% "scalatest" % "2.1.3" % "test", diff --git a/src/main/scala/jacks/jacks.scala b/src/main/scala/jacks/jacks.scala index 18da863..62697ff 100644 --- a/src/main/scala/jacks/jacks.scala +++ b/src/main/scala/jacks/jacks.scala @@ -10,30 +10,39 @@ import scala.collection.convert.Wrappers.JConcurrentMapWrapper import java.io._ import java.util.concurrent.ConcurrentHashMap +import reflect.runtime.universe._ + trait JacksMapper { val mapper = new ObjectMapper mapper.registerModule(new ScalaModule) - def readValue[T: Manifest](src: Array[Byte]): T = mapper.readValue(src, resolve) - def readValue[T: Manifest](src: InputStream): T = mapper.readValue(src, resolve) - def readValue[T: Manifest](src: Reader): T = mapper.readValue(src, resolve) - def readValue[T: Manifest](src: String): T = mapper.readValue(src, resolve) + def readValue[T: TypeTag](src: Array[Byte]): T = mapper.readValue(src, resolve[T]) + def readValue[T: TypeTag](src: InputStream): T = mapper.readValue(src, resolve[T]) + def readValue[T: TypeTag](src: Reader): T = mapper.readValue(src, resolve[T]) + def readValue[T: TypeTag](src: String): T = mapper.readValue(src, resolve[T]) def writeValue(w: Writer, v: Any) { mapper.writeValue(w, v) } def writeValue(o: OutputStream, v: Any) { mapper.writeValue(o, v) } - def writeValueAsString[T: Manifest](v: T) = writerWithType.writeValueAsString(v) + def writeValueAsString[T: TypeTag](v: T) = writerWithType[T].writeValueAsString(v) + + def writerWithType[T: TypeTag] = mapper.writerWithType(resolve[T]) + + val cache = JConcurrentMapWrapper(new ConcurrentHashMap[Type, JavaType]) - def writerWithType[T: Manifest] = mapper.writerWithType(resolve) + def resolve[T: TypeTag]: JavaType = resolve(typeOf[T]) - val cache = JConcurrentMapWrapper(new ConcurrentHashMap[Manifest[_], JavaType]) + def resolve(t: Type): JavaType = cache.getOrElseUpdate(t, { + + val cm = reflect.runtime.currentMirror + def runtimeClass(t: Type) = cm.runtimeClass(t.erasure) + val typeArguments = t.typeArgs.map(resolve) - def resolve(implicit m: Manifest[_]): JavaType = cache.getOrElseUpdate(m, { - def params = m.typeArguments.map(resolve(_)) val tf = mapper.getTypeFactory - m.typeArguments.isEmpty match { - case true => tf.constructType(m.erasure) - case false => tf.constructParametricType(m.erasure, params: _*) - } + + if (t <:< typeOf[Null]) tf.constructType(classOf[Any]) + else if (typeArguments.isEmpty || t <:< typeOf[Array[_]]) tf.constructType(runtimeClass(t)) + else tf.constructParametricType(runtimeClass(t), typeArguments: _*) + }) } diff --git a/src/main/scala/jacks/module.scala b/src/main/scala/jacks/module.scala index dd118ff..db6ab33 100644 --- a/src/main/scala/jacks/module.scala +++ b/src/main/scala/jacks/module.scala @@ -21,7 +21,8 @@ import com.fasterxml.jackson.databind.introspect._ import java.lang.annotation.Annotation import java.lang.reflect.{Constructor, Method} -import tools.scalap.scalax.rules.scalasig.ScalaSig +import reflect.runtime.universe._ +import reflect.runtime.{currentMirror=>mirror} class ScalaModule extends Module { def version = new Version(2, 3, 3, null, "com.lambdaworks", "jacks") @@ -81,7 +82,7 @@ class ScalaDeserializers extends Deserializers.Base { case Some(sts) if sts.isCaseClass => new CaseClassDeserializer(t, sts.creator(cfg)) case _ => null } - } else if (classOf[Symbol].isAssignableFrom(cls)) { + } else if (classOf[scala.Symbol].isAssignableFrom(cls)) { new SymbolDeserializer } else if (classOf[AnyRef].equals(cls)) { new UntypedObjectDeserializer(cfg) @@ -91,7 +92,8 @@ class ScalaDeserializers extends Deserializers.Base { } def companion[T](cls: Class[_]): T = { - Class.forName(cls.getName + "$").getField("MODULE$").get(null).asInstanceOf[T] + val companionSymbol = mirror.classSymbol(cls).companion.asModule + mirror.reflectModule(companionSymbol).instance.asInstanceOf[T] } lazy val orderings = Map[Class[_], Ordering[_]]( @@ -137,7 +139,7 @@ class ScalaSerializers extends Serializers.Base { case Some(sts) if sts.isCaseClass => new CaseClassSerializer(t, sts.annotatedAccessors(cfg)) case _ => null } - } else if (classOf[Symbol].isAssignableFrom(cls)) { + } else if (classOf[scala.Symbol].isAssignableFrom(cls)) { new SymbolSerializer(t) } else if (classOf[Enumeration$Val].isAssignableFrom(cls)) { ser.std.ToStringSerializer.instance @@ -157,18 +159,11 @@ case class Accessor( include: Include = ALWAYS ) -class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig) { - import tools.scalap.scalax.rules.scalasig.{Method => _, _} - import ScalaTypeSig.findClass +class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val cls: ClassSymbol) { - val cls = { - val name = `type`.getRawClass.getCanonicalName.replace('$', '.') - sig.symbols.collectFirst { - case c:ClassSymbol if c.path == name => c - }.get - } + import ScalaTypeSig.findClass - def isCaseClass = cls.isCase + def isCaseClass = cls.isCaseClass def constructor(cfg: MapperConfig[_]): Constructor[_] = { val types = accessors(cfg).map(_.`type`.getRawClass) @@ -199,17 +194,16 @@ class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig) def accessors(cfg: MapperConfig[_]): List[Accessor] = { var index = 1 - cls.children.foldLeft(List.newBuilder[Accessor]) { - (accessors, c) => - if (c.isCaseAccessor && !c.isPrivate) { + cls.toType.decls.foldLeft(List.newBuilder[Accessor]) { + case (accessors, c: MethodSymbol) if (c.isCaseAccessor && !c.isPrivate) => val sym = c.asInstanceOf[MethodSymbol] val name = sym.name - val typ = resolve(sym.infoType) + val typ = resolve(sym.returnType) val dflt = default(`type`.getRawClass, "apply", index) - accessors += Accessor(name, typ, dflt, external(cfg, name)) + accessors += Accessor(name.encodedName.toString, typ, dflt, external(cfg, name.encodedName.toString)) index += 1 - } - accessors + accessors + case (accessors, _) => accessors }.result } @@ -267,37 +261,26 @@ class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig) i => (`type`.containedTypeName(i) -> `type`.containedType(i)) }.toMap - def resolve(t: Type): JavaType = t match { - case NullaryMethodType(t2) => - resolve(t2) - case TypeRefType(_, TypeSymbol(s), Nil) => - contained(s.name) - case TypeRefType(_, s, Nil) => - tf.constructType(ScalaTypeSig.resolve(s)) - case TypeRefType(_, s, a :: Nil) if s.path == "scala.Array" => - ArrayType.construct(resolve(a), null, null) - case TypeRefType(_, s, args) => - val params = args.map(resolve(_)) - tf.constructParametricType(ScalaTypeSig.resolve(s), params: _ *) + def resolve(t: Type): JavaType = { + + val typeName = t.typeSymbol.name.toString + if (contained.contains(typeName)) contained(typeName) + else if (t.typeArgs.isEmpty) tf.constructType(ScalaTypeSig.resolve(t.typeSymbol)) + else if (t <:< typeOf[Array[_]]) ArrayType.construct(resolve(t.typeArgs.head), null, null) + else { + val params = t.typeArgs.map(resolve(_)) + tf.constructParametricType(ScalaTypeSig.resolve(t.typeSymbol), params: _*) + } } + } object ScalaTypeSig { - import tools.scalap.scalax.rules.scalasig.{ScalaSigParser, Symbol} import scala.collection.immutable._ def apply(tf: TypeFactory, t: JavaType): Option[ScalaTypeSig] = { - def findSig(cls: Class[_]): Option[ScalaSig] = { - ScalaSigParser.parse(cls) orElse { - val enc = cls.getEnclosingClass - if (enc != null) findSig(enc) else None - } - } - - findSig(t.getRawClass) match { - case Some(sig) => Some(new ScalaTypeSig(tf, t, sig)) - case None => None - } + val cls = Option(t.getRawClass) + cls.map(x => new ScalaTypeSig(tf, t, mirror.classSymbol(x))) } val types = Map[String, Class[_]]( @@ -320,7 +303,7 @@ object ScalaTypeSig { "scala.Enumeration.Value" -> classOf[Enumeration$Val] ).withDefault(findClass(_)) - def resolve(s: Symbol) = types(s.path) + def resolve(s: Symbol) = types(s.asType.toType.erasure.typeSymbol.fullName) def findClass(name: String): Class[_] = { val cl = Thread.currentThread().getContextClassLoader() diff --git a/src/test/scala/jacks/spec.scala b/src/test/scala/jacks/spec.scala index f05efa9..2a59eac 100644 --- a/src/test/scala/jacks/spec.scala +++ b/src/test/scala/jacks/spec.scala @@ -7,6 +7,8 @@ import org.scalacheck.Prop.forAll import scala.beans.BeanProperty +import reflect.runtime.universe.TypeTag + object ImmutableCollectionSpec extends ScalaModuleSpec("") { import scala.collection.immutable._ import ImmutableCollections._ @@ -201,16 +203,16 @@ case class CaseClass( abstract class ScalaModuleSpec(name: String) extends Properties(name) { import JacksMapper._ - def r[T](s: String)(implicit m: Manifest[T]): T = readValue(s) + def r[T: TypeTag](s: String): T = readValue(s) - def rw[T](v: T)(implicit m: Manifest[T]): T = { - val t = resolve(m) + def rw[T: TypeTag](v: T): T = { + val t = resolve[T] val s = mapper.writeValueAsString(v) mapper.readValue(s, t) } - def rw[T,U](v: T, v2: U)(implicit m: Manifest[U]): U = { - val u = resolve(m) + def rw[T,U: TypeTag](v: T, v2: U): U = { + val u = resolve[U] val s = mapper.writeValueAsString(v) mapper.readValue(s, u) } diff --git a/src/test/scala/jacks/test.scala b/src/test/scala/jacks/test.scala index 81a2169..fd8bff5 100644 --- a/src/test/scala/jacks/test.scala +++ b/src/test/scala/jacks/test.scala @@ -9,6 +9,15 @@ import com.fasterxml.jackson.databind.JsonMappingException import org.scalatest.FunSuite import org.scalatest.Matchers +import reflect.runtime.universe.TypeTag + +object TaggedWrapper { + type Tagged = Long with ({ type Tag = Nothing }) +} +import TaggedWrapper._ + +case class TaggedWrapper(tagged: Tagged) + case class Primitives( boolean: Boolean = true, byte: Byte = 0, @@ -268,9 +277,20 @@ class JacksMapperSuite extends JacksTestSuite { } test("resolve caches JavaType") { - val m = manifest[String] - resolve(m) should be theSameInstanceAs resolve(m) + resolve[String] should be theSameInstanceAs resolve[String] } + + test("tagged type") { + val t: Tagged = 3l.asInstanceOf[Tagged] + rw(t) should equal (t) + } + + test("inner tagged type") { + val t: Tagged = 3l.asInstanceOf[Tagged] + val wrapper = TaggedWrapper(t) + rw(wrapper) should equal (wrapper) + } + } class UntypedObjectDeserializerSuite extends JacksTestSuite { @@ -294,7 +314,7 @@ class UntypedObjectDeserializerSuite extends JacksTestSuite { trait JacksTestSuite extends FunSuite with Matchers { import JacksMapper._ - def rw[T: Manifest](v: T) = read(write(v)) - def write[T: Manifest](v: T) = writeValueAsString(v) - def read[T: Manifest](s: String) = readValue(s) + def rw[T: TypeTag](v: T) = read(write(v)) + def write[T: TypeTag](v: T) = writeValueAsString(v) + def read[T: TypeTag](s: String) = readValue(s) }