alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Play Framework/Scala example source code file (JsMacroImpl.scala)

This example Play Framework source code file (JsMacroImpl.scala) is included in my "Source Code Warehouse" project. The intent of this project is to help you more easily find Play Framework (and Scala) source code examples by using tags.

All credit for the original source code belongs to Play Framework; I'm just trying to make examples easier to find. (For my Scala work, see my Scala examples and tutorials.)

Play Framework tags/keywords

api, apply, boolean, context, json, lib, library, list, nil, no, play framework, reflection, select, some, string, typeref

The JsMacroImpl.scala Play Framework example source code

/*
 * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
 */
package play.api.libs.json

import scala.language.higherKinds
import scala.reflect.macros.Context
import language.experimental.macros

object JsMacroImpl {

  def formatImpl[A: c.WeakTypeTag](c: Context): c.Expr[Format[A]] =
    macroImpl[A, Format](c, "format", "inmap", reads = true, writes = true)

  def readsImpl[A: c.WeakTypeTag](c: Context): c.Expr[Reads[A]] =
    macroImpl[A, Reads](c, "read", "map", reads = true, writes = false)

  def writesImpl[A: c.WeakTypeTag](c: Context): c.Expr[Writes[A]] =
    macroImpl[A, Writes](c, "write", "contramap", reads = false, writes = true)

  def macroImpl[A, M[_]](c: Context, methodName: String, mapLikeMethod: String, reads: Boolean, writes: Boolean)(implicit atag: c.WeakTypeTag[A], matag: c.WeakTypeTag[M[A]]): c.Expr[M[A]] = {

    val nullableMethodName = s"${methodName}Nullable"
    val lazyMethodName = s"lazy${methodName.capitalize}"

    def conditionalList[T](ifReads: T, ifWrites: T): List[T] =
      (if (reads) List(ifReads) else Nil) :::
        (if (writes) List(ifWrites) else Nil)

    import c.universe._
    import c.universe.Flag._

    val companioned = weakTypeOf[A].typeSymbol
    val companionSymbol = companioned.companionSymbol
    val companionType = companionSymbol.typeSignature

    val libsPkg = Select(Select(Ident(newTermName("play")), newTermName("api")), newTermName("libs"))
    val jsonPkg = Select(libsPkg, newTermName("json"))
    val functionalSyntaxPkg = Select(Select(libsPkg, newTermName("functional")), newTermName("syntax"))
    val utilPkg = Select(jsonPkg, newTermName("util"))

    val jsPathSelect = Select(jsonPkg, newTermName("JsPath"))
    val readsSelect = Select(jsonPkg, newTermName("Reads"))
    val writesSelect = Select(jsonPkg, newTermName("Writes"))
    val unliftIdent = Select(functionalSyntaxPkg, newTermName("unlift"))
    val lazyHelperSelect = Select(utilPkg, newTypeName("LazyHelper"))

    val unapply =
      companionType.declaration(stringToTermName("unapply")) match {
        case NoSymbol => c.abort(c.enclosingPosition, "No unapply function found")
        case s => s.asMethod
      }

    val unapplyReturnTypes = unapply.returnType match {
      case TypeRef(_, _, Nil) =>
        c.abort(c.enclosingPosition, s"Unapply of ${companionSymbol} has no parameters. Are you using an empty case class?")
      case TypeRef(_, _, args) =>
        args.head match {
          case t @ TypeRef(_, _, Nil) => Some(List(t))
          case t @ TypeRef(_, _, args) =>
            if (t <:< typeOf[Option[_]]) Some(List(t))
            else if (t <:< typeOf[Seq[_]]) Some(List(t))
            else if (t <:< typeOf[Set[_]]) Some(List(t))
            else if (t <:< typeOf[Map[_, _]]) Some(List(t))
            else if (t <:< typeOf[Product]) Some(args)
          case _ => None
        }
      case _ => None
    }

    //println("Unapply return type:" + unapplyReturnTypes)

    val applies =
      companionType.declaration(stringToTermName("apply")) match {
        case NoSymbol => c.abort(c.enclosingPosition, "No apply function found")
        case s => s.asMethod.alternatives
      }

    // searches apply method corresponding to unapply
    val apply = applies.collectFirst {
      case (apply: MethodSymbol) if (apply.paramss.headOption.map(_.map(_.asTerm.typeSignature)) == unapplyReturnTypes) => apply
    }

    val params = apply match {
      case Some(apply) => apply.paramss.head //verify there is a single parameter group
      case None => c.abort(c.enclosingPosition, "No apply function found matching unapply parameters")
    }

    //println("apply found:" + apply)

    final case class Implicit(paramName: Name, paramType: Type, neededImplicit: Tree, isRecursive: Boolean, tpe: Type)

    val inferredImplicits = params.map { param =>

      val implType = param.typeSignature
      val (isRecursive, tpe) = implType match {
        case TypeRef(_, t, args) =>
          val isRec = args.exists(_.typeSymbol == companioned)
          // Option[_] needs special treatment because we need to use XXXOpt
          val tp = if (implType.typeConstructor <:< typeOf[Option[_]].typeConstructor) args.head else implType
          (isRec, tp)
        case TypeRef(_, t, _) =>
          (false, implType)
      }

      // builds M implicit from expected type
      val neededImplicitType = appliedType(matag.tpe.typeConstructor, tpe :: Nil)
      // infers implicit
      val neededImplicit = c.inferImplicitValue(neededImplicitType)
      Implicit(param.name, implType, neededImplicit, isRecursive, tpe)
    }

    //println("Found implicits:"+inferredImplicits)

    // if any implicit is missing, abort
    val missingImplicits = inferredImplicits.collect { case Implicit(_, t, impl, rec, _) if (impl == EmptyTree && !rec) => t }
    if (missingImplicits.nonEmpty)
      c.abort(c.enclosingPosition, s"No implicit format for ${missingImplicits.mkString(", ")} available.")

    val helperMember = Select(This(tpnme.EMPTY), newTermName("lazyStuff"))
    def callHelper(target: Tree, methodName: String): Tree =
      Apply(Select(target, newTermName(methodName)), List(helperMember))
    def readsWritesHelper(methodName: String): List[Tree] =
      conditionalList(readsSelect, writesSelect).map(s => callHelper(s, methodName))

    var hasRec = false

    // combines all reads into CanBuildX
    val canBuild = inferredImplicits.map {
      case Implicit(name, t, impl, rec, tpe) =>
        // inception of (__ \ name).read(impl)
        val jspathTree = Apply(
          Select(jsPathSelect, newTermName(scala.reflect.NameTransformer.encode("\\"))),
          List(Literal(Constant(name.decoded)))
        )

        if (!rec) {
          val callMethod = if (t.typeConstructor <:< typeOf[Option[_]].typeConstructor) nullableMethodName else methodName
          Apply(
            Select(jspathTree, newTermName(callMethod)),
            List(impl)
          )
        } else {
          hasRec = true
          if (t.typeConstructor <:< typeOf[Option[_]].typeConstructor)
            Apply(
              Select(jspathTree, newTermName(nullableMethodName)),
              callHelper(Apply(jsPathSelect, Nil), lazyMethodName) :: Nil
            )
          else {
            Apply(
              Select(jspathTree, newTermName(lazyMethodName)),
              if (tpe.typeConstructor <:< typeOf[List[_]].typeConstructor)
                readsWritesHelper("list")
              else if (tpe.typeConstructor <:< typeOf[Set[_]].typeConstructor)
                readsWritesHelper("set")
              else if (tpe.typeConstructor <:< typeOf[Seq[_]].typeConstructor)
                readsWritesHelper("seq")
              else if (tpe.typeConstructor <:< typeOf[Map[_, _]].typeConstructor)
                readsWritesHelper("map")
              else List(helperMember)
            )
          }
        }
    }.reduceLeft { (acc, r) =>
      Apply(
        Select(acc, newTermName("and")),
        List(r)
      )
    }

    // builds the final M[A] using apply method
    //val applyMethod = Ident( companionSymbol )
    val applyMethod =
      Function(
        params.foldLeft(List[ValDef]())((l, e) =>
          l :+ ValDef(Modifiers(PARAM), newTermName(e.name.encoded), TypeTree(), EmptyTree)
        ),
        Apply(
          Select(Ident(companionSymbol), newTermName("apply")),
          params.foldLeft(List[Tree]())((l, e) =>
            l :+ Ident(newTermName(e.name.encoded))
          )
        )
      )

    val unapplyMethod = Apply(
      unliftIdent,
      List(
        Select(Ident(companionSymbol), unapply.name)
      )
    )

    // if case class has one single field, needs to use inmap instead of canbuild.apply
    val method = if (params.length > 1) "apply" else mapLikeMethod
    val finalTree = Apply(
      Select(canBuild, newTermName(method)),
      conditionalList(applyMethod, unapplyMethod)
    )
    //println("finalTree: "+finalTree)

    val importFunctionalSyntax = Import(functionalSyntaxPkg, List(ImportSelector(nme.WILDCARD, -1, null, -1)))
    if (!hasRec) {
      val block = Block(
        importFunctionalSyntax,
        finalTree
      )
      //println("block:"+block)
      c.Expr[M[A]](block)
    } else {
      val helper = newTermName("helper")
      val helperVal = ValDef(
        Modifiers(),
        helper,
        Ident(weakTypeOf[play.api.libs.json.util.LazyHelper[M, A]].typeSymbol),
        Apply(Ident(newTermName("LazyHelper")), List(finalTree))
      )

      val block = Select(
        Block(
          importFunctionalSyntax,
          ClassDef(
            Modifiers(Flag.FINAL),
            newTypeName("$anon"),
            List(),
            Template(
              List(
                AppliedTypeTree(
                  lazyHelperSelect,
                  List(
                    Ident(matag.tpe.typeSymbol),
                    Ident(atag.tpe.typeSymbol)
                  )
                )
              ),
              emptyValDef,
              List(
                DefDef(
                  Modifiers(),
                  nme.CONSTRUCTOR,
                  List(),
                  List(List()),
                  TypeTree(),
                  Block(
                    Apply(
                      Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR),
                      List()
                    )
                  )
                ),
                ValDef(
                  Modifiers(Flag.OVERRIDE | Flag.LAZY),
                  newTermName("lazyStuff"),
                  AppliedTypeTree(Ident(matag.tpe.typeSymbol), List(TypeTree(atag.tpe))),
                  finalTree
                )
              )
            )
          ),
          Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List())
        ),
        newTermName("lazyStuff")
      )

      //println("block:"+block)

      c.Expr[M[A]](block)
    }
  }
}

Other Play Framework source code examples

Here is a short list of links related to this Play Framework JsMacroImpl.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.