Skip to content
This repository was archived by the owner on Nov 27, 2018. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions project/build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 22 additions & 13 deletions src/main/scala/jacks/jacks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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: _*)

})
}

Expand Down
75 changes: 29 additions & 46 deletions src/main/scala/jacks/module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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[_]](
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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[_]](
Expand All @@ -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()
Expand Down
12 changes: 7 additions & 5 deletions src/test/scala/jacks/spec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand Down Expand Up @@ -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)
}
Expand Down
30 changes: 25 additions & 5 deletions src/test/scala/jacks/test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}