Permalink
Browse files

Scala 2.9 support for JSON

  • Loading branch information...
Joni Freeman
Joni Freeman committed May 14, 2011
1 parent 0414b82 commit a7bebd75336d8ed3314027f135b1e24781c712d4
View
@@ -61,6 +61,7 @@ Download following jars:
* http://scala-tools.org/repo-releases/net/liftweb/lift-json/XXX/lift-json-XXX.jar
* http://mirrors.ibiblio.org/pub/mirrors/maven2/com/thoughtworks/paranamer/paranamer/2.1/paranamer-2.1.jar
+* scalap (Only for Scala-2.9 compatible versions)
Extras
------
@@ -215,7 +215,7 @@ object Extraction {
case o: JObject =>
formats.fieldSerializer(a.getClass).map { serializer =>
val constructorArgNames =
- Reflection.constructorArgs(a.getClass, constructor, formats.parameterNameReader).map(_._1).toSet
+ Reflection.constructorArgs(a.getClass, constructor, formats.parameterNameReader, None).map(_._1).toSet
val jsonFields = o.obj.map { f =>
val JField(n, v) = (serializer.deserializer orElse Map(f -> f))(f)
(n, (n, v))
@@ -227,7 +227,11 @@ object Extraction {
fieldsToSet.foreach { case (name, typeInfo) =>
jsonFields.get(name).foreach { case (n, v) =>
val typeArgs = typeInfo.parameterizedType
- .map(_.getActualTypeArguments.map(_.asInstanceOf[Class[_]]).toList)
+ .map(_.getActualTypeArguments.map(_.asInstanceOf[Class[_]]).toList.zipWithIndex
+ .map { case (t, idx) =>
+ if (t == classOf[java.lang.Object]) ScalaSigReader.readField(name, a.getClass, idx)
+ else t
+ })
val value = extract0(v, typeInfo.clazz, typeArgs.getOrElse(Nil))
Reflection.setField(a, n, value)
}
@@ -76,6 +76,9 @@ private[json] object Meta {
case class DeclaredConstructor(constructor: JConstructor[_], args: List[Arg])
+ // Current constructor parsing context. (containingClass + allArgs could be replaced with Constructor)
+ case class Context(argName: String, containingClass: Class[_], allArgs: List[(String, Type)])
+
private val mappings = new Memo[Type, Mapping]
private val unmangledNames = new Memo[String, String]
private val paranamer = new CachingParanamer(new BytecodeReadingParanamer)
@@ -89,28 +92,28 @@ private[json] object Meta {
(implicit formats: Formats): Mapping = {
import Reflection._
- def constructors(t: Type, visited: Set[Type]) = {
- Reflection.constructors(t, formats.parameterNameReader).map { case (c, args) =>
+ def constructors(t: Type, visited: Set[Type], context: Option[Context]) = {
+ Reflection.constructors(t, formats.parameterNameReader, context).map { case (c, args) =>
DeclaredConstructor(c, args.map { case (name, t) =>
- toArg(unmangleName(name), t, visited) })
+ toArg(unmangleName(name), t, visited, Context(name, c.getDeclaringClass, args)) })
}
}
- def toArg(name: String, genericType: Type, visited: Set[Type]): Arg = {
+ def toArg(name: String, genericType: Type, visited: Set[Type], context: Context): Arg = {
def mkContainer(t: Type, k: Kind, valueTypeIndex: Int, factory: Mapping => Mapping) =
if (typeConstructor_?(t)) {
val typeArgs = typeConstructors(t, k)(valueTypeIndex)
factory(fieldMapping(typeArgs)._1)
- } else factory(fieldMapping(typeParameters(t, k)(valueTypeIndex))._1)
+ } else factory(fieldMapping(typeParameters(t, k, context)(valueTypeIndex))._1)
def parameterizedTypeOpt(t: Type) = t match {
case x: ParameterizedType => Some(x)
case _ => None
}
- def mkConstructor(t: Type) =
+ def mkConstructor(t: Type) =
if (visited.contains(t)) (Cycle(t), false)
- else (Constructor(TypeInfo(rawClassOf(t), parameterizedTypeOpt(t)), constructors(t, visited + t)), false)
+ else (Constructor(TypeInfo(rawClassOf(t), parameterizedTypeOpt(t)), constructors(t, visited + t, Some(context))), false)
def fieldMapping(t: Type): (Mapping, Boolean) = t match {
case pType: ParameterizedType =>
@@ -151,7 +154,7 @@ private[json] object Meta {
val typeInfo =
if (typeArgs.isEmpty) TypeInfo(c, None)
else TypeInfo(c, Some(mkParameterizedType(c, typeArgs)))
- Constructor(typeInfo, constructors(t, Set()))
+ Constructor(typeInfo, constructors(t, Set(), None))
})
}
}
@@ -209,11 +212,11 @@ private[json] object Meta {
classOf[java.lang.Short], classOf[Date], classOf[Symbol], classOf[JValue],
classOf[JObject], classOf[JArray]).map((_, ())))
- def constructors(t: Type, names: ParameterNameReader): List[(JConstructor[_], List[(String, Type)])] =
- rawClassOf(t).getDeclaredConstructors.map(c => (c, constructorArgs(t, c, names))).toList
+ def constructors(t: Type, names: ParameterNameReader, context: Option[Context]): List[(JConstructor[_], List[(String, Type)])] =
+ rawClassOf(t).getDeclaredConstructors.map(c => (c, constructorArgs(t, c, names, context))).toList
def constructorArgs(t: Type, constructor: JConstructor[_],
- nameReader: ParameterNameReader): List[(String, Type)] = {
+ nameReader: ParameterNameReader, context: Option[Context]): List[(String, Type)] = {
def argsInfo(c: JConstructor[_], typeArgs: Map[TypeVariable[_], Type]) = {
val Name = """^((?:[^$]|[$][^0-9]+)+)([$][0-9]+)?$"""r
def clean(name: String) = name match {
@@ -222,7 +225,11 @@ private[json] object Meta {
try {
val names = nameReader.lookupParameterNames(c).map(clean)
val types = c.getGenericParameterTypes.toList map {
- case v: TypeVariable[_] => typeArgs.getOrElse(v, v)
+ case v: TypeVariable[_] =>
+ val arg = typeArgs.getOrElse(v, v)
+ if (arg == classOf[java.lang.Object])
+ context.map(ctx => ScalaSigReader.readConstructor(ctx.argName, ctx.containingClass, ctx.allArgs.map(_._1))).getOrElse(arg)
+ else arg
case x => x
}
names.toList.zip(types)
@@ -244,13 +251,16 @@ private[json] object Meta {
def primaryConstructorArgs(c: Class[_])(implicit formats: Formats) = {
val ord = Ordering[Int].on[JConstructor[_]](_.getParameterTypes.size)
val primary = c.getDeclaredConstructors.max(ord)
- constructorArgs(c, primary, formats.parameterNameReader)
+ constructorArgs(c, primary, formats.parameterNameReader, None)
}
- def typeParameters(t: Type, k: Kind): List[Class[_]] = {
+ def typeParameters(t: Type, k: Kind, context: Context): List[Class[_]] = {
def term(i: Int) = t match {
case ptype: ParameterizedType => ptype.getActualTypeArguments()(i) match {
- case c: Class[_] => c
+ case c: Class[_] =>
+ if (c == classOf[java.lang.Object])
+ ScalaSigReader.readConstructor(context.argName, context.containingClass, context.allArgs.map(_._1))
+ else c
case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]]
case x => fail("do not know how to get type parameter from " + x)
}
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009-2010 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package json
+
+import scala.tools.scalap.scalax.rules.scalasig._
+
+private[json] object ScalaSigReader {
+ def readConstructor(argName: String, clazz: Class[_], argNames: List[String]): Class[_] = {
+ val cl = findClass(clazz)
+ val cstr = findConstructor(cl, argNames).getOrElse(Meta.fail("Can't find constructor " + clazz))
+ findArgType(cstr, argNames.indexOf(argName))
+ }
+
+ def readField(name: String, clazz: Class[_], typeArgIndex: Int): Class[_] = {
+ def read(current: Class[_]): MethodSymbol = {
+ if (current == null)
+ Meta.fail("Can't find field " + name + " from " + clazz)
+ else
+ findField(findClass(current), name).getOrElse(read(current.getSuperclass))
+ }
+ findArgTypeF(read(clazz), typeArgIndex)
+ }
+
+ private def findClass(clazz: Class[_]): ClassSymbol = {
+ val sig = findScalaSig(clazz).getOrElse(Meta.fail("Can't find ScalaSig for " + clazz))
+ findClass(sig, clazz).getOrElse(Meta.fail("Can't find " + clazz + " from parsed ScalaSig"))
+ }
+
+ private def findClass(sig: ScalaSig, clazz: Class[_]): Option[ClassSymbol] = {
+ sig.symbols.collect { case c: ClassSymbol => c }.find(_.name == clazz.getSimpleName).orElse {
+ sig.topLevelClasses.find(_.symbolInfo.name == clazz.getSimpleName).orElse {
+ sig.topLevelObjects.map { obj =>
+ val t = obj.infoType.asInstanceOf[TypeRefType]
+ t.symbol.children collect { case c: ClassSymbol => c } find(_.symbolInfo.name == clazz.getSimpleName)
+ }.head
+ }
+ }
+ }
+
+ private def findConstructor(c: ClassSymbol, argNames: List[String]): Option[MethodSymbol] = {
+ val ms = c.children collect { case m: MethodSymbol if m.name == "<init>" => m }
+ ms.find(m => m.children.map(_.name) == argNames)
+ }
+
+ private def findField(c: ClassSymbol, name: String): Option[MethodSymbol] =
+ (c.children collect { case m: MethodSymbol if m.name == name => m }).headOption
+
+ private def findArgType(s: MethodSymbol, argIdx: Int): Class[_] = {
+ def findPrimitive(t: Type): Symbol = t match {
+ case TypeRefType(_, _, TypeRefType(ThisType(_), symbol, _) :: xs) => symbol
+ case TypeRefType(_, _, (ref @ TypeRefType(_, _, _)) :: xs) => findPrimitive(ref)
+ case x => Meta.fail("Unexpected type info " + x)
+ }
+ toClass(findPrimitive(s.children(argIdx).asInstanceOf[SymbolInfoSymbol].infoType))
+ }
+
+ private def findArgTypeF(s: MethodSymbol, typeArgIdx: Int): Class[_] = {
+ // FIXME can be removed when 2.8 no loner needs to supported.
+ // 2.8 does not have NullaryMethodType, work around that.
+ /*
+ val t = s.infoType match {
+ case NullaryMethodType(TypeRefType(_, _, args)) => args(typeArgIdx)
+ }
+ */
+ val t = s.infoType.asInstanceOf[{ def resultType: Type }].resultType match {
+ case TypeRefType(_, _, args) => args(typeArgIdx)
+ }
+
+ def findPrimitive(t: Type): Symbol = t match {
+ case TypeRefType(ThisType(_), symbol, _) => symbol
+ case ref @ TypeRefType(_, _, _) => findPrimitive(ref)
+ case x => Meta.fail("Unexpected type info " + x)
+ }
+ toClass(findPrimitive(t))
+ }
+
+ private def toClass(s: Symbol) = s.path match {
+ case "scala.Short" => classOf[Short]
+ case "scala.Int" => classOf[Int]
+ case "scala.Long" => classOf[Long]
+ case "scala.Boolean" => classOf[Boolean]
+ case "scala.Float" => classOf[Float]
+ case "scala.Double" => classOf[Double]
+ case _ => classOf[AnyRef]
+ }
+
+ private def findScalaSig(clazz: Class[_]): Option[ScalaSig] =
+ ScalaSigParser.parse(clazz).orElse(findScalaSig(clazz.getDeclaringClass))
+}
@@ -42,8 +42,8 @@ object XmlBugs extends Specification {
val example2 = <word term="example" self="http://localhost:8080/word/example" available="true"></word>
val expected2 = """{"self":"http://localhost:8080/word/example","term":"example","available":"true"}"""
- Printer.compact(render(toJson(example1))) mustEqual expected1
- Printer.compact(render(toJson(example2))) mustEqual expected2
+ (toJson(example1) diff parse(expected1)) mustEqual Diff(JNothing, JNothing, JNothing)
+ (toJson(example2) diff parse(expected2)) mustEqual Diff(JNothing, JNothing, JNothing)
}
"Nodes with attributes converted to correct JSON" in {
@@ -163,7 +163,7 @@ object XmlExamples extends Specification("XML Examples") {
val a1 = attrToObject("stats", "count", s => JInt(s.s.toInt)) _
val a2 = attrToObject("messages", "href", identity) _
val json = a1(a2(toJson(messageXml1)))
- compact(render(json)) mustEqual expected1
+ (json diff parse(expected1)) mustEqual Diff(JNothing, JNothing, JNothing)
}
"Example with one attribute, one nested element " in {
@@ -26,12 +26,13 @@ class LiftFrameworkProject(info: ProjectInfo) extends ParentProject(info) with L
import CompileScope._
import ProvidedScope._
+ val scalap = "org.scala-lang" % "scalap" % buildScalaVersion
// Core projects
// -------------
lazy val common = coreProject("common", slf4j_api, logback, log4j)()
lazy val actor = coreProject("actor")(common)
- lazy val json = coreProject("json", paranamer)()
+ lazy val json = coreProject("json", paranamer, scalap)()
lazy val json_scalaz = coreProject("json-scalaz", scalaz)(json)
lazy val json_ext = coreProject("json-ext", commons_codec, joda_time)(common, json)
lazy val util = coreProject("util", joda_time, commons_codec, javamail, log4j, htmlparser)(actor, json)

0 comments on commit a7bebd7

Please sign in to comment.