All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rojoma.json.v3.-impl.util.AutomaticJsonCodecImpl.scala Maven / Gradle / Ivy

The newest version!
package com.rojoma.json.v3
package `-impl`.util

import java.nio.charset.StandardCharsets

import MacroCompat._
import util._

abstract class AutomaticJsonCodecImpl extends MacroCompat {
  val c: Context
  import c.universe._

  val bs: Seq[c.Expr[Any]]

  val requestType: Class[_]

  def defaultCompanion(cls: ClassDef) = {
    val objModFlags = Seq(Flag.PRIVATE, Flag.PROTECTED, Flag.LOCAL).foldLeft(syntheticFlag) { (set, flag) =>
      if(cls.mods.hasFlag(flag)) set | flag
      else set
    }
    val objMod = Modifiers(objModFlags, cls.mods.privateWithin, Nil)

    if(cls.mods.hasFlag(Flag.CASE)) {
      // if the primary ctor has one argument list, we want to
      // extend AbstractFunctionN and Serializable, otherwise
      // just Serializable.

      // I'm pretty sure the primary ctor will always be first..
      val ctorInfo = cls.impl.children.collectFirst {
        case DefDef(mods, name, _, pparams, _, _) if name.decodedName.toString == "" =>
          pparams
      }

      val parents = ctorInfo match {
        case Some(List(paramList)) if paramList.length < 23 && cls.tparams.isEmpty =>
          val afn = toTypeName("AbstractFunction" + paramList.length)
          val params = paramList.map { param =>
            val Scala = toTermName("Scala") // 2.10 grr
            val Repeated = toTypeName("") // 2.10 grr
            param.tpt match {
              case AppliedTypeTree(Select(Select(Ident(termNames.ROOTPKG), Scala), Repeated), List(t)) =>
                tq"_root_.scala.Seq[$t]"
              case other =>
                other
            }
          }
          List(tq"_root_.scala.runtime.$afn[..$params, ${cls.name}]", tq"_root_.scala.Serializable")
        case _ => List(tq"_root_.scala.Serializable")
      }

      q"""$objMod object ${cls.name.toTermName} extends ..$parents {
             override def toString = ${cls.name.decodedName.toString}
          }"""
    } else {
      q"$objMod object ${cls.name.toTermName} {}"
    }
  }

  lazy val (cls, companion) = bs.map(_.tree) match {
    case List(cls: ClassDef) => (cls, defaultCompanion(cls))
    case List(cls: ClassDef, companion: ModuleDef) => (cls, companion)
    case _ =>
      c.abort(c.enclosingPosition, s"${requestType.getSimpleName} must be used on a class")
  }

  lazy val tparamNames = cls.tparams.map(_.name)

  // Hm.  I'm not sure a freshName would be guaranteed to be stable.
  // So we'll do this instead:
  def sha1sum(s: String) = {
    val md = java.security.MessageDigest.getInstance("SHA1")
    md.digest(s.getBytes(StandardCharsets.UTF_8)).map(_.toInt & 0xff).map("%02x".format(_)).mkString
  }
  def generatedName(typ: String) = toTermName("automatically generated json " + typ + " " + sha1sum(cls.name.encodedName.toString))

  def augmentedCompanion(t: Tree, params: Seq[Tree], resultType: Tree, companion: ModuleDef, codecType: String) = {
    val addition =
      if(params.isEmpty) q"implicit val ${generatedName(codecType)}: $resultType = $t"
      else q"implicit def ${generatedName(codecType)}[..${cls.tparams}](implicit ..$params): $resultType = $t"

    ModuleDef(companion.mods, companion.name, Template(companion.impl.parents, companion.impl.self, companion.impl.body :+ addition))
  }

  def encodeCompanion(companion: ModuleDef) =
    augmentedCompanion(q"_root_.com.rojoma.json.v3.util.AutomaticJsonEncodeBuilder.apply[${cls.name}[..$tparamNames]]",
                       tparamNames.zipWithIndex.map { case (t, i) =>
                         ValDef(Modifiers(syntheticFlag), toTermName(s"e$i"), tq"_root_.com.rojoma.json.v3.codec.JsonEncode[$t]", q"")
                       },
                       tq"_root_.com.rojoma.json.v3.codec.JsonEncode[${cls.name}[..$tparamNames]]",
                       companion,
                       "encode")

  def decodeCompanion(companion: ModuleDef) =
    augmentedCompanion(q"_root_.com.rojoma.json.v3.util.AutomaticJsonDecodeBuilder.apply[${cls.name}[..$tparamNames]]",
                       tparamNames.zipWithIndex.map { case (t, i) =>
                         ValDef(Modifiers(syntheticFlag), toTermName(s"d$i"), tq"_root_.com.rojoma.json.v3.codec.JsonDecode[$t]", q"")
                       },
                       tq"_root_.com.rojoma.json.v3.codec.JsonDecode[${cls.name}[..$tparamNames]]",
                       companion,
                       "decode")

  def codec = {
    val newCompanion = encodeCompanion(decodeCompanion(companion))
    val tree = q"$cls; $newCompanion"
    // println(tree)
    c.Expr[Any](tree)
  }

  def encode = {
    val newCompanion = encodeCompanion(companion)
    val tree = q"$cls; $newCompanion"
    // println(tree)
    c.Expr[Any](tree)
  }

  def decode = {
    val newCompanion = decodeCompanion(companion)
    val tree = q"$cls; $newCompanion"
    // println(tree)
    c.Expr[Any](tree)
  }
}

object AutomaticJsonCodecImpl {
  def codec(ctx: Context)(annottees: ctx.Expr[Any]*): ctx.Expr[Any] = {
    val b = new {
      val c: ctx.type = ctx
      val bs = annottees
      val requestType = classOf[AutomaticJsonCodec]
    } with AutomaticJsonCodecImpl

    b.codec
  }

  def encode(ctx: Context)(annottees: ctx.Expr[Any]*): ctx.Expr[Any] = {
    val b = new {
      val c: ctx.type = ctx
      val bs = annottees
      val requestType = classOf[AutomaticJsonEncode]
    } with AutomaticJsonCodecImpl

    b.encode
  }

  def decode(ctx: Context)(annottees: ctx.Expr[Any]*): ctx.Expr[Any] = {
    val b = new {
      val c: ctx.type = ctx
      val bs = annottees
      val requestType = classOf[AutomaticJsonDecode]
    } with AutomaticJsonCodecImpl

    b.decode
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy