org.bitlap.tools.internal.javaCompatibleMacro.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2023 bitlap
*
* 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 org.bitlap.tools.internal
import scala.reflect.macros.whitebox
/** @author
* 梦境迷离
* @since 2021/11/23
* @version 1.0
*/
object javaCompatibleMacro {
class JavaCompatibleProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._
/** We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
private def getNoArgsContrWithCurrying(
annotteeClassParams: List[List[Tree]],
annotteeClassDefinitions: Seq[Tree]
): Tree = {
annotteeClassDefinitions.foreach {
case defDef: DefDef if defDef.name.decodedName.toString == "this" && defDef.vparamss.isEmpty =>
c.abort(
defDef.pos,
"Non-parameter constructor method has already defined, please remove it or not use'@javaCompatible'"
)
case _ =>
}
val acsVals = annotteeClassParams.map(valDefAccessors)
acsVals.foreach(valDefs =>
valDefs.foreach(valDef =>
if (valDef.mods.hasFlag(Flag.PRIVATE)) {
c.abort(c.enclosingPosition, "`@javaCompatible` can be applied only to non-private fields")
}
)
)
val defaultParameters = acsVals.map(params => params.map(_.zeroValue))
if (annotteeClassParams.isEmpty || annotteeClassParams.size == 1) {
q"""
def this() = {
this(..${defaultParameters.flatten})
}
"""
} else {
q"""
def this() = {
this(..${defaultParameters.head})(...${defaultParameters.tail})
}
"""
}
}
private def replaceAnnotation(valDefTree: Tree): Tree = {
val safeValDef = valDefAccessors(Seq(valDefTree)).head
val mods = safeValDef.mods.mapAnnotations { f =>
if (!f.toString().contains("BeanProperty")) f ++ List(q"new _root_.scala.beans.BeanProperty") else f
}
ValDef(mods, safeValDef.name, safeValDef.tpt, safeValDef.rhs)
}
private def getClassWithBeanProperty(classDecl: ClassDef): Tree = {
val q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" = classDecl
val newFieldss = paramss.asInstanceOf[List[List[Tree]]].map(_.map(replaceAnnotation))
q"$mods class $tpname[..$tparams] $ctorMods(...$newFieldss) extends ..$bases { ..$body }"
}
override def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val tmpClassDefTree = appendClassBody(
classDecl,
classInfo => List(getNoArgsContrWithCurrying(classInfo.classParamss, classInfo.body))
)
val rest = getClassWithBeanProperty(tmpClassDefTree)
c.Expr(q"""
${compDeclOpt.fold(EmptyTree)(x => x)}
$rest
""")
}
override def checkAnnottees(annottees: Seq[c.universe.Expr[Any]]): Unit = {
super.checkAnnottees(annottees)
val annotateeClass: ClassDef = checkClassDef(annottees)
if (!isCaseClass(annotateeClass)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CASE_CLASS)
}
}
}
}