json.scala 3.4 KB
Newer Older
梦境迷离's avatar
梦境迷离 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
package io.github.dreamylost

import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

/**
 * annotation for case classes
 *
 * @author 梦境迷离
 * @since 2021/6/13
 * @version 1.0
 */
@compileTimeOnly("enable macro to expand macro annotations")
final class json extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro jsonMacro.impl
}

object jsonMacro {
  def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    def jsonFormatter(className: TypeName, fields: List[Tree]): c.universe.Tree = {
      fields.length match {
        case 0 => c.abort(c.enclosingPosition, "Cannot create json formatter for case class with no fields")
        case _ =>
          c.info(c.enclosingPosition, s"jsonFormatter className: $className, field length: ${fields.length}", force = true)
          q"implicit val jsonAnnotationFormat = play.api.libs.json.Json.format[$className]"
      }
    }

    def modifiedCompanion(compDeclOpt: Option[ModuleDef], format: Tree, className: TypeName): c.universe.Tree = {
      compDeclOpt map { compDecl =>
        // Add the formatter to the existing companion object
        val q"object $obj extends ..$bases { ..$body }" = compDecl
        val o =
          q"""
          object $obj extends ..$bases {
            ..$body
            $format
          }
        """
        c.info(c.enclosingPosition, s"modifiedCompanion className: $className, exists obj: $o", force = true)
        o
      } getOrElse {
        // Create a companion object with the formatter
        val o = q"object ${className.toTermName} { $format }"
        c.info(c.enclosingPosition, s"modifiedCompanion className: $className, new obj: $o", force = true)
        o
      }
    }

    def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): c.Expr[Nothing] = {
      val (className, fields) = classDecl match {
        case q"$mods class $className(..$fields) extends ..$bases { ..$body }" =>
          if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
            c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl, mods: $mods")
          } else {
            c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
            (className, fields)
          }
        case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl")
      }
      c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
      className match {
        case t: TypeName =>
          val format = jsonFormatter(t, fields.asInstanceOf[List[Tree]])
          val compDecl = modifiedCompanion(compDeclOpt, format, t)
          c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true)
          // Return both the class and companion object declarations
          c.Expr(
            q"""
        $classDecl
        $compDecl
      """)
      }

    }

梦境迷离's avatar
梦境迷离 已提交
80 81
    c.info(c.enclosingPosition, s"json annottees: $annottees", true)

梦境迷离's avatar
梦境迷离 已提交
82 83 84 85 86 87 88
    annottees.map(_.tree) match {
      case (classDecl: ClassDef) :: Nil => modifiedDeclaration(classDecl)
      case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifiedDeclaration(classDecl, Some(compDecl))
      case _ => c.abort(c.enclosingPosition, "Invalid annottee")
    }
  }
}