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

1.add Accessor

2.support jacksonEnum
上级 4b14b488
......@@ -31,6 +31,7 @@ Learn Scala macro and abstract syntax tree.
- `@apply`
- `@constructor`
- `@equalsAndHashCode`
- `@jacksonEnum`
> Annotations involving interaction are supported in the idea plug-in (named `Scala-Macro-Tools` in Marketplace).
......
......@@ -46,6 +46,7 @@
- `@apply`
- `@constructor`
- `@equalsAndHashCode`
- `@jacksonEnum`
> 涉及到交互操作的注解在IDEA插件中都得到了支持。在插件市场中搜索`Scala-Macro-Tools`可下载。
......
......@@ -260,3 +260,44 @@ class Person extends scala.AnyRef {
}
}
```
## @jacksonEnum
`@jacksonEnum`注解用于为类的主构造函数中的所有Scala枚举类型的参数提供`Jackson`序列化的支持。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `nonTypeRefers` 指定不需要创建`Jackson``TypeReference`子类的枚举类型。
- 支持`case class``class`
- 如果枚举类型存在`TypeReference`的子类,则不会生成新的子类,也不会重复添加`@JsonScalaEnumeration`注解到参数上。这主要用于解决冲突问题。
- 示例
```scala
@jacksonEnum(nonTypeRefers = Seq("EnumType"))
class B(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
```
宏生成的中间代码:
```scala
class EnumType2TypeRefer extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[EnumType2.type] {
def <init>() = {
super.<init>();
()
}
};
class B extends scala.AnyRef {
<paramaccessor> var enum1: JacksonEnumTest.this.EnumType.EnumType = _;
@new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) <paramaccessor> private[this] val enum2: JacksonEnumTest.this.EnumType2.EnumType2 = _;
<paramaccessor> private[this] val i: Int = _;
def <init>(enum1: JacksonEnumTest.this.EnumType.EnumType, @new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) enum2: JacksonEnumTest.this.EnumType2.EnumType2 = EnumType2.A, i: Int) = {
super.<init>();
()
}
};
()
```
\ No newline at end of file
......@@ -266,3 +266,45 @@ class Person extends scala.AnyRef {
}
}
```
## @jacksonEnum
The `jacksonEnum` annotation is used to provide `Jackson` serialization support for all Scala enumeration type parameters in the primary constructor of the class.
- Note
- `verbose` Whether to enable detailed log.
- `nonTypeRefers` Specifies the enumeration type of the `TypeReference` subclass of `Jackson` that does not need to be created.
- Support `case class` and `class`.
- If the enumeration type has subclasses of `TypeReference`, no new subclasses will be generated,
and `JsonScalaEnumeration` annotation will not be added to the parameters repeatedly. This is mainly used to solve conflict problems.
- Example
```scala
@jacksonEnum(nonTypeRefers = Seq("EnumType"))
class B(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
```
Macro expansion code:
```scala
class EnumType2TypeRefer extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[EnumType2.type] {
def <init>() = {
super.<init>();
()
}
};
class B extends scala.AnyRef {
<paramaccessor> var enum1: JacksonEnumTest.this.EnumType.EnumType = _;
@new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) <paramaccessor> private[this] val enum2: JacksonEnumTest.this.EnumType2.EnumType2 = _;
<paramaccessor> private[this] val i: Int = _;
def <init>(enum1: JacksonEnumTest.this.EnumType.EnumType, @new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) enum2: JacksonEnumTest.this.EnumType2.EnumType2 = EnumType2.A, i: Int) = {
super.<init>();
()
}
};
()
```
......@@ -36,8 +36,8 @@ import scala.annotation.{ compileTimeOnly, StaticAnnotation }
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class jacksonEnum(
verbose: Boolean = false,
nonTypeRefers: Seq[String] = Nil
verbose: Boolean = false,
nonTypeRefers: Seq[String] = Nil
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro jacksonEnumMacro.JacksonEnumProcessor.impl
......
......@@ -366,37 +366,28 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))
}
private [macros] case class Accessor(
mods: Modifiers,
name: TermName,
paramType: Type,
rhs: Tree
private[macros] case class Accessor(
mods: Modifiers,
name: TermName,
paramType: Type,
rhs: Tree
) {
def typeName: TypeName = symbol.name.toTypeName
def symbol: c.universe.Symbol = paramType.typeSymbol
}
/**
* Retrieves the accessor fields on a case class and returns an iterable of tuples of the form Name -> Type.
* For every single field in a case class, a reference to the string name and string type of the field are returned.
*
* Example:
*
* {{{
* case class Test(id: UUID, name: String, age: Int)
*
* accessors(Test) = Seq("id" -> "UUID", "name" -> "String", age: "Int")
* }}}
* Retrieves the accessor fields on a case class and returns a Seq of Accessor.
*
* @param params The list of params retrieved from the case class
* @param params The list of params retrieved from the class
* @return An Sequence of tuples where each tuple encodes the string name and string type of a field
*/
def accessors(params: Seq[ValDef]): Seq[Accessor] = {
params.map {
case ValDef(mods, name: TermName, tpt: Tree, rhs) => {
Accessor(mods, name, c.typecheck(tq"$tpt", c.TYPEmode).tpe, rhs)
Accessor(mods, name, c.typecheck(tq"$tpt", c.TYPEmode).tpe, rhs)
}
}
}
......
......@@ -57,7 +57,7 @@ object equalsAndHashCodeMacro {
..$originalStatus
..$tmpTree
"""
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${append} }"
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$append }"
}
val res = c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
printTree(force = extractArgumentsDetail._1, res.tree)
......@@ -78,7 +78,7 @@ object equalsAndHashCodeMacro {
// equals method
private def getEqualsMethod(className: TypeName, termNames: Seq[TermName], superClasses: Seq[Tree], annotteeClassDefinitions: Seq[Tree]): Tree = {
val existsCanEqual = getClassMemberDefDefs(annotteeClassDefinitions).exists {
case tree @ q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.asInstanceOf[TermName].decodedName.toString == "canEqual" && paramss.nonEmpty =>
case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.asInstanceOf[TermName].decodedName.toString == "canEqual" && paramss.nonEmpty =>
val params = paramss.asInstanceOf[List[List[Tree]]].flatten.map(pp => getMethodParamName(pp))
params.exists(p => p.decodedName.toString == "Any")
case _ => false
......@@ -128,9 +128,9 @@ object equalsAndHashCodeMacro {
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val allFieldsTermName = getClassConstructorValDefsFlatten(annotteeClassParams).filter(cf => isNotLocalClassMember(cf)).map(_.name.toTermName)
val allTernNames = allFieldsTermName ++ getInternalFieldsTermNameExcludeLocal(annotteeClassDefinitions)
val hash = getHashcodeMethod(allTernNames, superClasses)
val equals = getEqualsMethod(className, allTernNames, superClasses, annotteeClassDefinitions)
val allTermNames = allFieldsTermName ++ getInternalFieldsTermNameExcludeLocal(annotteeClassDefinitions)
val hash = getHashcodeMethod(allTermNames, superClasses)
val equals = getEqualsMethod(className, allTermNames, superClasses, annotteeClassDefinitions)
c.Expr(
q"""
..$equals
......
......@@ -31,8 +31,8 @@ object jacksonEnumMacro {
private val extractArgumentsDetail: Tuple2[Boolean, Seq[String]] = {
extractArgumentsTuple2 {
case q"new jacksonEnum(verbose=$verbose, nonTypeRefers=$typeRefer)" => Tuple2(evalTree(verbose.asInstanceOf[Tree]), evalTree(typeRefer.asInstanceOf[Tree]))
case q"new jacksonEnum(nonTypeRefers=$typeRefer)" => Tuple2(false, evalTree(typeRefer.asInstanceOf[Tree]))
case q"new jacksonEnum(verbose=$verbose, nonTypeRefers=$nonTypeRefers)" => Tuple2(evalTree(verbose.asInstanceOf[Tree]), evalTree(nonTypeRefers.asInstanceOf[Tree]))
case q"new jacksonEnum(nonTypeRefers=$nonTypeRefers)" => Tuple2(false, evalTree(nonTypeRefers.asInstanceOf[Tree]))
case q"new jacksonEnum()" => Tuple2(false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
......@@ -40,35 +40,35 @@ object jacksonEnumMacro {
private def getJacksonTypeReferClasses(valDefs: List[ValDef]): Seq[Tree] = {
val safeValDefs = accessors(valDefs)
// is Enum ?
// Enum ?
safeValDefs.filter(_.symbol.name.toTermName.toString == "Value").
map(buildTypeReferName).
map(_.toTermName).
map(getTypeTermName).
filter(v => !extractArgumentsDetail._2.contains(v.decodedName.toString)).
distinct.
map(c => q"""class ${TypeName(c.decodedName.toString + "TypeRefer")} extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[$c.type]""")
}
private def buildTypeReferName(valDefTree: Tree): c.universe.TypeName = {
private def getTypeTermName(valDefTree: Tree): c.universe.TermName = {
val safeValDef = accessors(Seq(valDefTree.asInstanceOf[ValDef])).head
buildTypeReferName(safeValDef)
getTypeTermName(safeValDef)
}
private def buildTypeReferName(accessor: Accessor): c.universe.TypeName = {
private def getTypeTermName(accessor: Accessor): c.universe.TermName = {
val paramTypeStr = accessor.paramType.toString
TypeName(paramTypeStr.split("\\.").last)
TermName(paramTypeStr.split("\\.").last)
}
private def getAnnotation(valDefTree: Tree): Tree = {
q"new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[${TypeName(buildTypeReferName(valDefTree).decodedName.toString + "TypeRefer")}])"
q"new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[${TypeName(getTypeTermName(valDefTree).decodedName.toString + "TypeRefer")}])"
}
private def replaceFieldJsonEnumAnnotation(valDefTree: Tree): Tree = {
private def replaceAnnotation(valDefTree: Tree): Tree = {
val safeValDef = accessors(Seq(valDefTree.asInstanceOf[ValDef])).head
if (safeValDef.symbol.name.toTermName.toString == "Value") {
// duplication should be removed
val mods = safeValDef.mods.mapAnnotations(f => {
if (!f.toString().contains("JsonScalaEnumeration")) f ++ List(getAnnotation(valDefTree)) else f
if (!f.toString().contains("JsonScalaEnumeration") &&
!extractArgumentsDetail._2.contains(getTypeTermName(safeValDef).decodedName.toString)) f ++ List(getAnnotation(valDefTree)) else f
})
if (safeValDef.mods.hasFlag(Flag.MUTABLE)) {
q"$mods var ${safeValDef.name}: ${safeValDef.paramType} = ${safeValDef.rhs}"
......@@ -83,17 +83,16 @@ object jacksonEnumMacro {
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
// get class
val classDef = checkAndGetClassDef(annottees: _*)
// get field after replacing annotation for each field in constructor
val newClass = modifiedDeclaration(classDef, None).asInstanceOf[Expr[Nothing]]
// return all typeReferClasses and new classDef
val resTree = classDef match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
val valDefs = paramss.asInstanceOf[List[List[Tree]]].flatten.map(_.asInstanceOf[ValDef])
val typeReferClasses = getJacksonTypeReferClasses(valDefs).distinct
val newClass = modifiedDeclaration(classDef, None).asInstanceOf[Expr[Nothing]]
q"""
..$typeReferClasses
$newClass
$newClass // get field after replacing annotation for each field in constructor
"""
case _ => c.abort(c.enclosingPosition, ErrorMessage.ONLY_CLASS)
}
......@@ -106,7 +105,7 @@ object jacksonEnumMacro {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
val fieldss = paramss.asInstanceOf[List[List[Tree]]]
q"""
$mods class $tpname[..$tparams] $ctorMods(...${fieldss.map(_.map(replaceFieldJsonEnumAnnotation))}) extends ..$bases { ..$body }
$mods class $tpname[..$tparams] $ctorMods(...${fieldss.map(_.map(replaceAnnotation))}) extends ..$bases { ..$body }
"""
case _ => c.abort(c.enclosingPosition, ErrorMessage.ONLY_CLASS)
}
......
......@@ -53,24 +53,23 @@ object logMacro {
private def getLogType(logType: Tree): LogType = {
if (logType.children.exists(t => t.toString().contains(PACKAGE))) {
evalTree(logType.asInstanceOf[Tree]) // TODO remove asInstanceOf
evalTree(logType)
} else {
LogType.getLogType(logType.toString())
}
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val buildArg = (name: Name) => LogTransferArgument(name.toTermName.decodedName.toString, isClass = true)
val logTree = annottees.map(_.tree) match {
// Match a class, and expand, get class/object name.
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
val argument = LogTransferArgument(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(argument)
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
val argument = LogTransferArgument(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(argument)
case (classDef: ClassDef) :: Nil =>
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(buildArg(classDef.name))
case (moduleDef: ModuleDef) :: Nil =>
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(buildArg(moduleDef.name).copy(isClass = false))
case (classDef: ClassDef) :: (moduleDef: ModuleDef) :: Nil =>
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(buildArg(classDef.name))
case _ => c.abort(c.enclosingPosition, ErrorMessage.ONLY_OBJECT_CLASS)
}
// add result into class
val resTree = annottees.map(_.tree) match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
......
/*
* Copyright (c) 2021 jxnu-liguobin && contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.dreamylost
import org.scalatest.flatspec.AnyFlatSpec
......@@ -22,34 +43,39 @@ class JacksonEnumTest extends AnyFlatSpec with Matchers {
val A, B = Value
}
object EnumType3 extends Enumeration {
type EnumType3 = Value
val A, B = Value
}
"jacksonEnum1" should "ok" in {
class EnumTypeTypeRefer extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[EnumType.type]
case class A(
@JsonScalaEnumeration(classOf[EnumTypeTypeRefer]) enum1: EnumType.EnumType,
enum2: EnumType.EnumType = EnumType.A
@JsonScalaEnumeration(classOf[EnumTypeTypeRefer]) enum1: EnumType.EnumType,
enum2: EnumType.EnumType = EnumType.A
)
}
"jacksonEnum2" should "ok" in {
@jacksonEnum
case class A(
enum1: EnumType.EnumType,
enum2: EnumType.EnumType = EnumType.A,
i: Int)
enum1: EnumType.EnumType,
enum2: EnumType.EnumType = EnumType.A,
i: Int)
}
"jacksonEnum3" should "ok" in {
@jacksonEnum
case class A(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
@jacksonEnum(nonTypeRefers = Seq("EnumType", "EnumType2")) // Because it has been created
class B(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
var enum1: EnumType.EnumType, // No annotation will add
val enum2: EnumType2.EnumType2 = EnumType2.A,
val enum3: EnumType3.EnumType3,
i: Int)
}
"jacksonEnum4" should "ok when duplication" in {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册