提交 99db9fc4 编写于 作者: 梦境迷离's avatar 梦境迷离

add `@log` and refactor code

上级 e1f5fd02
package io.github.dreamylost
/**
*
* @author 梦境迷离
* @since 2021/6/29
* @version 1.0
*/
object LogMain extends App {
private val log: java.util.logging.Logger = java.util.logging.Logger.getLogger(LogMain.getClass.getName)
println(LogMain.getClass.getName)
println(log)
}
package io.github.dreamylost
import scala.reflect.macros.whitebox
/**
* Common methods
*
* @author 梦境迷离
* @since 2021/6/28
* @version 1.0
*/
trait MacroCommon {
def printTree(c: whitebox.Context)(force: Boolean, resTree: c.Tree): Unit = {
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = force
)
}
/**
* Check the class and its accompanying objects, and return the class definition.
*
* @param c
* @param annottees
* @return Return ClassDef
*/
def checkAndReturnClass(c: whitebox.Context)(annottees: c.Expr[Any]*): c.universe.ClassDef = {
import c.universe._
val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => classDecl
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => classDecl
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
}
annotateeClass
}
/**
* Modify the associated object itself according to whether there is an associated object.
*
* @param c
* @param annottees
* @param modifyAction The dependent type need aux-pattern in scala2. Now let's get around this.
* @return Return the result of modifyAction
*/
def handleWithImplType(c: whitebox.Context)(annottees: c.Expr[Any]*)
(modifyAction: (c.universe.ClassDef, Option[c.universe.ModuleDef]) => Any): c.Expr[Nothing] = {
import c.universe._
annottees.map(_.tree) match {
case (classDecl: ClassDef) :: Nil => modifyAction(classDecl, None).asInstanceOf[c.Expr[Nothing]]
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifyAction(classDecl, Some(compDecl)).asInstanceOf[c.Expr[Nothing]]
case _ => c.abort(c.enclosingPosition, "Invalid annottee")
}
}
/**
* Expand the class and check whether the class is a case class.
*
* @param c
* @param annotateeClass classDef
* @return Return true if it is a case class
*/
def isCaseClass(c: whitebox.Context)(annotateeClass: c.universe.ClassDef): Boolean = {
import c.universe._
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.warning(c.enclosingPosition, "'toString' annotation is used on 'case class'.")
true
} else false
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class. classDef: $annotateeClass")
}
}
}
......@@ -16,7 +16,7 @@ final class builder extends StaticAnnotation {
}
object builderMacro {
object builderMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
......@@ -98,7 +98,8 @@ object builderMacro {
}
}
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): c.Expr[Nothing] = {
// The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (mods, className, fields) = classDecl match {
case q"$mods class $className(..$fields) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
......@@ -123,18 +124,9 @@ object builderMacro {
c.info(c.enclosingPosition, s"builder annottees: $annottees", true)
val resTree = 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")
}
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = true, resTree.tree)
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = true
)
resTree
}
}
......@@ -16,7 +16,7 @@ final class json extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro jsonMacro.impl
}
object jsonMacro {
object jsonMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
......@@ -50,7 +50,8 @@ object jsonMacro {
}
}
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): c.Expr[Nothing] = {
// The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fields) = classDecl match {
case q"$mods class $className(..$fields) extends ..$bases { ..$body }" =>
if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
......@@ -77,12 +78,10 @@ object jsonMacro {
}
c.info(c.enclosingPosition, s"json annottees: $annottees", true)
c.info(c.enclosingPosition, s"json annottees: $annottees", force = true)
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = true, resTree.tree)
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")
}
resTree
}
}
package io.github.dreamylost
import io.github.dreamylost.LogType.LogType
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @param logType Specifies the type of `log` that needs to be generated
* @since 2021/6/28
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class log(
verbose: Boolean = false,
logType: LogType.LogType = LogType.JLog
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro logMacro.impl
}
sealed trait BaseLog {
val typ: LogType
def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree
}
object LogType extends Enumeration {
type LogType = Value
val JLog, Log4j2, Slf4j = Value
private lazy val types = Map(
JLog -> JBaseLogImpl,
Log4j2 -> Log4J2Impl,
Slf4j -> Slf4jImpl
)
def getLogImpl(logType: LogType): BaseLog = {
types.getOrElse(logType, default = throw new Exception(s"Not support log: $logType"))
}
}
object JBaseLogImpl extends BaseLog {
override val typ: LogType = LogType.JLog
override def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree = {
import c.universe._
if (isClass) {
q"""private val log: java.util.logging.Logger = java.util.logging.Logger.getLogger(classOf[${TypeName(t)}].getName)"""
} else {
q"""private val log: java.util.logging.Logger = java.util.logging.Logger.getLogger(${TermName(t)}.getClass.getName)"""
}
}
}
object Log4J2Impl extends BaseLog {
override val typ: LogType = LogType.Log4j2
override def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree = ???
}
object Slf4jImpl extends BaseLog {
override val typ: LogType = LogType.Slf4j
override def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree = ???
}
object logMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args: (Boolean, LogType) = c.prefix.tree match {
case q"new log(logType=$logType)" => (false, c.eval[LogType](c.Expr(logType)))
case q"new log(verbose=$verbose)" => (c.eval[Boolean](c.Expr(verbose)), LogType.JLog)
case q"new log(verbose=$verbose, logType=$logType)" => (c.eval[Boolean](c.Expr(verbose)), c.eval[LogType](c.Expr(logType)))
case q"new log()" => (false, LogType.JLog)
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args._1)
val logTree = annottees.map(_.tree) match {
// Match a class, and expand, get class/object name.
case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
case (classDef @ q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class or object.")
}
// add result into class
val resTree = annottees.map(_.tree) match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }"
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }"
}
printTree(c)(force = args._1, resTree)
c.Expr[Any](resTree)
}
}
......@@ -20,7 +20,7 @@ final class synchronized(
def macroTransform(annottees: Any*): Any = macro synchronizedMacro.impl
}
object synchronizedMacro {
object synchronizedMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
......@@ -47,12 +47,7 @@ object synchronizedMacro {
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a method")
}
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = args._1
)
printTree(c)(args._1, resTree)
c.Expr[Any](resTree)
}
}
......@@ -29,7 +29,7 @@ final class toString(
final case class Argument(verbose: Boolean, includeInternalFields: Boolean, includeFieldNames: Boolean, callSuper: Boolean)
object stringMacro {
object stringMacro extends MacroCommon {
def printField(c: whitebox.Context)(argument: Argument, lastParam: Option[String], field: c.universe.Tree): c.universe.Tree = {
import c.universe._
......@@ -71,7 +71,7 @@ object stringMacro {
case _: ValDef => true
case mem: MemberDef =>
c.info(c.enclosingPosition, s"MemberDef: ${mem.toString}", force = argument.verbose)
if (mem.toString().startsWith("override def toString")) {
if (mem.toString().startsWith("override def toString")) { // TODO better way
c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
}
false
......@@ -131,20 +131,8 @@ object stringMacro {
val argument = Argument(arg._1, arg._2, arg._3, arg._4)
c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => classDecl
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => classDecl
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
}
val isCase: Boolean = {
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.warning(c.enclosingPosition, "'toString' annotation is used on 'case class'.")
true
} else false
}
}
val annotateeClass: ClassDef = checkAndReturnClass(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
val resMethod = toStringTemplateImpl(c)(argument, annotateeClass)
......@@ -152,12 +140,7 @@ object stringMacro {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(resMethod)} }"
}
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = argument.verbose
)
printTree(c)(argument.verbose, resTree)
c.Expr[Any](resTree)
}
}
package io.github.dreamylost
import org.scalatest.{ FlatSpec, Matchers }
/**
*
* @author 梦境迷离
* @since 2021/6/28
* @version 1.0
*/
class LogTest extends FlatSpec with Matchers {
"log1" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@log class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.JLog) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.JLog) class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log2" should "ok on case class" in {
"""@log(verbose=true) case class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@log case class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() case class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) case class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.JLog) case class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.JLog) case class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log3" should "ok on object" in {
"""@log(verbose=true) object TestClass1 {
log.info("hello")
}""" should compile
"""@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile
"""@log(verbose=true) object TestClass4""" should compile
"""@log(logType=io.github.dreamylost.LogType.JLog) object TestClass5""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.JLog) object TestClass6""" should compile
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册