未验证 提交 9179156e 编写于 作者: 梦境迷离's avatar 梦境迷离 提交者: GitHub

support options (#210)

* support options
上级 a0165f67
......@@ -21,9 +21,8 @@
package org.bitlap.common
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import scala.reflect.macros.whitebox
import org.bitlap.common.internal.CaseClassExtractorMacro
import scala.reflect.ClassTag
import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.universe._
......@@ -38,34 +37,7 @@ object CaseClassExtractor {
/** Using the characteristics of the product type to get the field value should force the conversion externally
* (safely).
*/
def ofValue[T <: Product](t: T, field: CaseClassField): Option[Any] = macro macroImpl[T]
def macroImpl[T: c.WeakTypeTag](
c: whitebox.Context
)(t: c.Expr[T], field: c.Expr[CaseClassField]): c.Expr[Option[Any]] = {
import c.universe._
// scalafmt: { maxColumn = 400 }
val tree =
q"""
if ($t == null) None else {
val _field = $field
_field.${TermName(CaseClassField.fieldNamesTermName)}.find(kv => kv._2 == _field.${TermName(CaseClassField.stringifyTermName)})
.map(kv => $t.productElement(kv._1))
}
"""
exprPrintTree[Option[Any]](c)(tree)
}
def exprPrintTree[Field: c.WeakTypeTag](c: whitebox.Context)(resTree: c.Tree): c.Expr[Field] = {
c.info(
c.enclosingPosition,
s"\n###### Time: ${ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)} Expanded macro start ######\n" + resTree
.toString() + "\n###### Expanded macro end ######\n",
force = false
)
c.Expr[Field](resTree)
}
def ofValue[T <: Product](t: T, field: CaseClassField): Option[Any] = macro CaseClassExtractorMacro.macroImpl[T]
/** Using scala reflect to get the field value (safely).
*/
......
......@@ -21,10 +21,7 @@
package org.bitlap.common
import org.bitlap.common.CaseClassExtractor.exprPrintTree
import scala.reflect.macros.whitebox
import scala.collection.Seq
import org.bitlap.common.internal.CaseClassFieldMacro
trait CaseClassField {
......@@ -44,61 +41,5 @@ object CaseClassField {
final val fieldTermName = "Field"
final val fieldNamesTermName = "fieldIndexNames"
def apply[T <: Product](field: T => Any): CaseClassField = macro selectFieldMacroImpl[T]
def selectFieldMacroImpl[T: c.WeakTypeTag](
c: whitebox.Context
)(field: c.Expr[T => Any]): c.Expr[CaseClassField] = {
import c.universe._
val packageName = q"_root_.org.bitlap.common"
val Function(_, Select(_, termName)) = field.tree
val caseClassParams = getCaseClassParams[T](c)
val fieldName = termName.decodedName.toString
val searchField =
caseClassParams.find(_.name.toTermName.decodedName.toString == fieldName)
val fieldType = searchField.map(f => c.typecheck(tq"$f", c.TYPEmode).tpe)
if (searchField.isEmpty || fieldType.isEmpty) {
c.abort(
c.enclosingPosition,
s"""Field name is invalid, "${c.weakTypeOf[T].resultType}" does not have a field named $fieldName!
|Please consider using "CaseClassField[T]($fieldName)" instead of "CaseClassField($fieldName)" """.stripMargin
)
}
val genericType = fieldType.get match {
case t if t <:< typeOf[Option[_]] =>
val genericType = t.dealias.typeArgs.head
tq"_root_.scala.Option[$genericType]"
case t if t <:< typeOf[Seq[_]] =>
val genericType = t.dealias.typeArgs.head
tq"_root_.scala.Seq[$genericType]"
case t if t <:< typeOf[List[_]] =>
val genericType = t.dealias.typeArgs.head
tq"_root_.scala.List[$genericType]"
case t => tq"$t"
}
val fieldNameTypeName = TermName(s"${CaseClassField.classNameTermName}$$$fieldName")
val res =
q"""
case object $fieldNameTypeName extends $packageName.${TypeName(CaseClassField.classNameTermName)} {
override def ${TermName(CaseClassField.stringifyTermName)}: String = $fieldName
override type ${TypeName(CaseClassField.fieldTermName)} = $genericType
override val ${TermName(CaseClassField.fieldNamesTermName)} =
(${caseClassParams.indices.toList} zip ${caseClassParams.map(_.name.decodedName.toString)}).toMap
}
$fieldNameTypeName
"""
exprPrintTree[CaseClassField](c)(res)
}
def getCaseClassParams[T: c.WeakTypeTag](c: whitebox.Context): List[c.Symbol] = {
import c.universe._
val parameters = c.weakTypeOf[T].resultType.member(TermName("<init>")).typeSignature.paramLists
if (parameters.size > 1) {
c.abort(c.enclosingPosition, "The constructor of case class has currying!")
}
parameters.flatten
}
def apply[T <: Product](field: T => Any): CaseClassField = macro CaseClassFieldMacro.selectFieldMacroImpl[T]
}
......@@ -46,4 +46,6 @@ object MacroCache {
lazy val classFieldTypeMapping: mutable.Map[Int, mutable.Map[String, Any]] = mutable.Map.empty
lazy val classFieldDefaultValueMapping: mutable.Map[Int, mutable.Map[String, Any]] = mutable.Map.empty
lazy val transformerOptionsMapping: mutable.Map[Int, mutable.Set[Options]] = mutable.Map.empty
}
/*
* Copyright (c) 2022 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.common
/** @author
* 梦境迷离
* @version 1.0,6/27/22
*/
sealed trait Options
object Options {
case object enableOptionDefaultsToNone extends Options
case object enableCollectionDefaultsToEmpty extends Options
case object disableCollectionDefaultsToEmpty extends Options
case object disableOptionDefaultsToNone extends Options
}
......@@ -20,6 +20,7 @@
*/
package org.bitlap.common
import org.bitlap.common.internal.TransformerMacro
/** @author
* 梦境迷离
......@@ -27,12 +28,15 @@ package org.bitlap.common
*/
class Transformable[From, To] {
/** @param selectFromField
/** Sets the `From` to `To` mapping relationship of the field type.
*
* When the map function returns a known constant value, it means that the type mapping becomes a set value.
*
* @param selectFromField
* Select the name of the field to be mapped in the `From` class.
* @param map
* Specify the type mapping of the field, which must be provided when the type is incompatible, or else attempt to
* search for an implicit `Transformer[FromField, ToField]` (a failed search will result in a compile failure).
*
* @tparam FromField
* field type
* @tparam ToField
......@@ -47,7 +51,9 @@ class Transformable[From, To] {
): Transformable[From, To] =
macro TransformerMacro.mapTypeImpl[From, To, FromField, ToField]
/** @param selectFromField
/** Sets the `From` to `To` mapping relationship of the field name.
*
* @param selectFromField
* Select the name of the field to be mapped in the `From` class.
* @param selectToField
* Select the name of the field to be mapped in the `To` class.
......@@ -66,14 +72,36 @@ class Transformable[From, To] {
): Transformable[From, To] =
macro TransformerMacro.mapNameImpl[From, To, FromField, ToField]
/** Defines default value for missing field to successfully create `To` object. This method has the lowest priority.
/** Defines a default value for missing field to successfully create `To` object. This method has a higher priority
* than `enableOptionDefaultsToNone` or `enableCollectionDefaultsToEmpty`.
*
* Only the `selectToField` field does not have the same name found in the `From` and is not in the name mapping.
* So, even if `enableCollectionDefaultsToEmpty` or `enableCollectionDefaultsToEmpty`, you can also use
* `setDefaultValue` method to set the initial value for a single field.
*/
@unchecked
def setDefaultValue[ToField](selectToField: To => ToField, defaultValue: ToField): Transformable[From, To] =
macro TransformerMacro.setDefaultValueImpl[From, To, ToField]
/** Sets target value of optional fields to `None` if field is missing from source type `From`.
*/
def enableOptionDefaultsToNone: Transformable[From, To] =
macro TransformerMacro.enableOptionDefaultsToNoneImpl[From, To]
/** Sets target value of collection fields to `empty` if field is missing from source type `From`.
*/
def enableCollectionDefaultsToEmpty: Transformable[From, To] =
macro TransformerMacro.enableCollectionDefaultsToEmptyImpl[From, To]
/** Disable `None` fallback value for optional fields in `To`. This is the default configuration option.
*/
def disableOptionDefaultsToNone: Transformable[From, To] =
macro TransformerMacro.disableOptionDefaultsToNoneImpl[From, To]
/** Disable `empty` fallback value for collection fields in `To`. Support List, Seq, Vector, Set. This is the default
* configuration option.
*/
def disableCollectionDefaultsToEmpty: Transformable[From, To] =
macro TransformerMacro.disableCollectionDefaultsToEmptyImpl[From, To]
def instance: Transformer[From, To] = macro TransformerMacro.instanceImpl[From, To]
}
......@@ -82,6 +110,7 @@ object Transformable {
/** Automatically derive `Transformable[From, To]` for case classes only, for non-case classes you should use the
* `setType` method to configure the mapping relationship.
*
* @tparam From
* @tparam To
* @return
......
......@@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.bitlap.common
package org.bitlap.common.internal
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
......@@ -44,7 +44,11 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
isOption: Boolean = false,
isVector: Boolean = false,
isSet: Boolean = false
)
) {
def isCollection: Boolean = isSeq || isList || isOption || isVector || isSet
def isStrictCollection: Boolean = isSeq || isList || isVector || isSet
}
final case class FieldTreeInformation(
index: Int,
......@@ -59,7 +63,9 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
fieldName: String,
fieldType: Type,
collectionFlags: CollectionFlags,
genericType: List[Type] = Nil
genericType: List[Type] = Nil,
hasDefaultValue: Boolean,
zeroValue: Tree
)
def tryGetOrElse(tree: Tree, default: Tree): Tree =
......@@ -82,7 +88,7 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
*/
def checkGetFieldTreeInformationList[T: WeakTypeTag](columnsFunc: TermName): List[FieldTreeInformation] = {
val idxColumn = (i: Int) => q"$columnsFunc()($i)"
val params = getCaseClassFieldInfo[T]()
val params = getCaseClassFieldInfoList[T]()
val paramsSize = params.size
val types = params.map(_.fieldType)
val indexColumns = (0 until paramsSize).toList.map(i => i -> idxColumn(i))
......@@ -91,46 +97,65 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
}
indexColumns zip types map { kv =>
val (isOption, isSeq, isList, isVector, isSet) = isWrapType(kv._2)
val typed = c.typecheck(tq"${kv._2}", c.TYPEmode).tpe
var genericType: List[Type] = Nil
if (isList || isSeq || isOption || isVector || isSet) {
val collectionFlag = isWrapType(kv._2)
val typed = c.typecheck(tq"${kv._2}", c.TYPEmode).tpe
var genericType: List[Type] = Nil
if (collectionFlag.isCollection) {
genericType = typed.dealias.typeArgs ::: genericType
}
FieldTreeInformation(
kv._1._1,
kv._1._2,
kv._2,
getDefaultValue(kv._2),
CollectionFlags(isSeq, isList, isOption, isVector, isSet),
getZeroValue(kv._2),
collectionFlag,
genericType
)
}
}
def getFieldDefaultValueMap[T: WeakTypeTag](init: MethodSymbol): Map[String, Tree] = {
val classSym = weakTypeOf[T].typeSymbol
init.paramLists.head
.map(_.asTerm)
.zipWithIndex
.flatMap { case (p, i) =>
if (!p.isParamWithDefault) None
else {
val getterName = TermName("apply$default$" + (i + 1))
Some(p.name.decodedName.toString -> q"${classSym.name.toTermName}.$getterName") // moduleSym is none
}
}
.toMap
}
/** Get only the symbol of the case class constructor parameters.
*
* @tparam T
* Type of the case class.
* @return
*/
def getCaseClassFieldInfo[T: WeakTypeTag](): List[FieldInformation] = {
val parameters = resolveParameters[T]
def getCaseClassFieldInfoList[T: WeakTypeTag](): List[FieldInformation] = {
val init = c.weakTypeOf[T].resultType.member(TermName("<init>")).asMethod
val defaultValuesTerm = getFieldDefaultValueMap[T](init)
val parameters = init.typeSignature.paramLists
if (parameters.size > 1) {
c.abort(c.enclosingPosition, "The constructor of case class has currying!")
}
parameters.flatten.map { p =>
val typed = c.typecheck(tq"$p", c.TYPEmode).tpe
var genericType: List[Type] = Nil
val (isOption, isSeq, isList, isVector, isSet) = isWrapType(typed)
if (isList || isSeq || isOption || isVector || isSet) {
val typed = c.typecheck(tq"$p", c.TYPEmode).tpe
var genericType: List[Type] = Nil
val collectionFlags = isWrapType(typed)
if (collectionFlags.isCollection) {
genericType = typed.dealias.typeArgs ::: genericType
}
FieldInformation(
p.name.decodedName.toString,
typed,
CollectionFlags(isSeq, isList, isOption, isVector, isSet),
genericType
collectionFlags,
genericType,
defaultValuesTerm.contains(p.name.decodedName.toString),
getZeroValue(typed)
)
}
}
......@@ -152,16 +177,6 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
c.Expr[T](resTree)
}
/** Get the constructor symbol of the case class.
*
* @tparam T
* Type of the case class.
* @return
* The parameters may be currying, so it's a two-level list.
*/
def resolveParameters[T: WeakTypeTag]: List[List[Symbol]] =
c.weakTypeOf[T].resultType.member(TermName("<init>")).typeSignature.paramLists
/** Get the `TypeName` of the class.
*
* @tparam T
......@@ -179,7 +194,7 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
* @return
*/
def checkGetFieldZipInformation[T: WeakTypeTag]: FieldZipInformation = {
val params = getCaseClassFieldInfo[T]()
val params = getCaseClassFieldInfoList[T]()
val paramsSize = params.size
val names = params.map(_.fieldName)
FieldZipInformation(
......@@ -196,7 +211,7 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
def getBuilderId(annoBuilderPrefix: String): Int =
c.prefix.actualType.toString.replace(annoBuilderPrefix, "").toInt
private def getDefaultValue(typ: Type): Tree =
def getZeroValue(typ: Type): Tree =
typ match {
case t if t =:= typeOf[Int] =>
q"0"
......@@ -226,9 +241,7 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
q"null"
}
private type OptionSeqListVectorSet = (Boolean, Boolean, Boolean, Boolean, Boolean)
private def isWrapType(typed: Type): OptionSeqListVectorSet = {
private def isWrapType(typed: Type): CollectionFlags = {
var isList: Boolean = false
var isSeq: Boolean = false
var isOption: Boolean = false
......@@ -247,7 +260,7 @@ abstract class AbstractMacroProcessor(val c: blackbox.Context) {
isSeq = true
case _ =>
}
Tuple5(isOption, isSeq, isList, isVector, isSet)
CollectionFlags(isSeq, isList, isOption, isVector, isSet)
}
}
/*
* Copyright (c) 2022 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.common.internal
import org.bitlap.common.CaseClassField
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import scala.reflect.macros.whitebox
/** @author
* 梦境迷离
* @version 1.0,6/27/22
*/
object CaseClassExtractorMacro {
def macroImpl[T](
c: whitebox.Context
)(t: c.Expr[T], field: c.Expr[CaseClassField]): c.Expr[Option[Any]] = {
import c.universe._
// scalafmt: { maxColumn = 400 }
val tree =
q"""
if ($t == null) None else {
val _field = $field
_field.${TermName(CaseClassField.fieldNamesTermName)}.find(kv => kv._2 == _field.${TermName(CaseClassField.stringifyTermName)})
.map(kv => $t.productElement(kv._1))
}
"""
c.info(
c.enclosingPosition,
s"\n###### Time: ${ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)} Expanded macro start ######\n" + tree
.toString() + "\n###### Expanded macro end ######\n",
force = false
)
c.Expr[Option[Any]](tree)
}
}
/*
* Copyright (c) 2022 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.common.internal
import org.bitlap.common.CaseClassField
import scala.collection.Seq
import scala.reflect.macros.whitebox
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
/** @author
* 梦境迷离
* @version 1.0,6/27/22
*/
object CaseClassFieldMacro {
def selectFieldMacroImpl[T: c.WeakTypeTag](
c: whitebox.Context
)(field: c.Expr[T => Any]): c.Expr[CaseClassField] = {
import c.universe._
val packageName = q"_root_.org.bitlap.common"
val Function(_, Select(_, termName)) = field.tree
val caseClassParams = getCaseClassParams[T](c)
val fieldName = termName.decodedName.toString
val searchField =
caseClassParams.find(_.name.toTermName.decodedName.toString == fieldName)
val fieldType = searchField.map(f => c.typecheck(tq"$f", c.TYPEmode).tpe)
if (searchField.isEmpty || fieldType.isEmpty) {
c.abort(
c.enclosingPosition,
s"""Field name is invalid, "${c.weakTypeOf[T].resultType}" does not have a field named $fieldName!
|Please consider using "CaseClassField[T]($fieldName)" instead of "CaseClassField($fieldName)" """.stripMargin
)
}
val genericType = fieldType.get match {
case t if t <:< typeOf[Option[_]] =>
val genericType = t.dealias.typeArgs.head
tq"_root_.scala.Option[$genericType]"
case t if t <:< typeOf[Seq[_]] =>
val genericType = t.dealias.typeArgs.head
tq"_root_.scala.Seq[$genericType]"
case t if t <:< typeOf[List[_]] =>
val genericType = t.dealias.typeArgs.head
tq"_root_.scala.List[$genericType]"
case t => tq"$t"
}
val fieldNameTypeName = TermName(s"${CaseClassField.classNameTermName}$$$fieldName")
val res =
q"""
case object $fieldNameTypeName extends $packageName.${TypeName(CaseClassField.classNameTermName)} {
override def ${TermName(CaseClassField.stringifyTermName)}: String = $fieldName
override type ${TypeName(CaseClassField.fieldTermName)} = $genericType
override val ${TermName(CaseClassField.fieldNamesTermName)} =
(${caseClassParams.indices.toList} zip ${caseClassParams.map(_.name.decodedName.toString)}).toMap
}
$fieldNameTypeName
"""
c.info(
c.enclosingPosition,
s"\n###### Time: ${ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)} Expanded macro start ######\n" + res
.toString() + "\n###### Expanded macro end ######\n",
force = false
)
c.Expr[CaseClassField](res)
}
def getCaseClassParams[T: c.WeakTypeTag](c: whitebox.Context): List[c.Symbol] = {
import c.universe._
val parameters = c.weakTypeOf[T].resultType.member(TermName("<init>")).typeSignature.paramLists
if (parameters.size > 1) {
c.abort(c.enclosingPosition, "The constructor of case class has currying!")
}
parameters.flatten
}
}
......@@ -19,9 +19,10 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.bitlap.common
package org.bitlap.common.internal
import org.bitlap.common.{ MacroCache, Options, Transformable, Transformer => BitlapTransformer }
import org.bitlap.common.{ Transformer => BitlapTransformer }
import scala.collection.mutable
import scala.reflect.macros.whitebox
......@@ -33,8 +34,6 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
import c.universe._
import scala.collection.immutable
protected val packageName = q"_root_.org.bitlap.common"
private val builderFunctionPrefix = "_TransformableFunction$"
private val builderDefaultValuePrefix$ = "_TransformableDefaultValue$"
......@@ -77,7 +76,54 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
MacroCache.classFieldNameMapping
.getOrElseUpdate(builderId, mutable.Map.empty)
.update(toName.decodedName.toString, fromName.decodedName.toString)
val tree = q"new ${c.prefix.actualType}"
exprPrintTree[Transformable[From, To]](force = false, tree)
}
def enableOptionDefaultsToNoneImpl[From, To] =
setOptions[From, To](
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.add(Options.enableOptionDefaultsToNone),
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.remove(Options.disableOptionDefaultsToNone)
)
def enableCollectionDefaultsToEmptyImpl[From, To] =
setOptions[From, To](
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.add(Options.enableCollectionDefaultsToEmpty),
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.remove(Options.disableCollectionDefaultsToEmpty)
)
def disableOptionDefaultsToNoneImpl[From, To] =
setOptions[From, To](
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.add(Options.disableOptionDefaultsToNone),
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.remove(Options.enableCollectionDefaultsToEmpty)
)
def disableCollectionDefaultsToEmptyImpl[From, To] =
setOptions[From, To](
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.add(Options.disableCollectionDefaultsToEmpty),
MacroCache.transformerOptionsMapping
.getOrElseUpdate(_, mutable.Set.empty)
.remove(Options.enableCollectionDefaultsToEmpty)
)
private def setOptions[From, To](enable: Int => Unit, disable: Int => Unit): Expr[Transformable[From, To]] = {
val builderId = getBuilderId(annoBuilderPrefix)
enable(builderId)
disable(builderId)
val tree = q"new ${c.prefix.actualType}"
exprPrintTree[Transformable[From, To]](force = false, tree)
}
......@@ -140,35 +186,13 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
private def getTransformBody[From: WeakTypeTag, To: WeakTypeTag]: Tree = {
val toClassName = resolveClassTypeName[To]
val fromClassName = resolveClassTypeName[From]
val toClassInfo = getCaseClassFieldInfo[To]()
val fromClassInfo = getCaseClassFieldInfo[From]()
val customDefaultValueMapping =
MacroCache.classFieldDefaultValueMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val toClassInfo = getCaseClassFieldInfoList[To]()
val fromClassInfo = getCaseClassFieldInfoList[From]()
val customFieldNameMapping =
MacroCache.classFieldNameMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val customFieldTypeMapping =
MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
c.info(c.enclosingPosition, s"Field default value mapping: $customDefaultValueMapping", force = true)
c.info(c.enclosingPosition, s"Field name mapping: $customFieldNameMapping", force = true)
c.info(c.enclosingPosition, s"Field type mapping: $customFieldTypeMapping", force = true)
val missingFields = toClassInfo.map(_.fieldName).filterNot(fromClassInfo.map(_.fieldName).contains)
val missingExcludeMappingName = missingFields.filterNot(customFieldNameMapping.contains)
if (missingExcludeMappingName.nonEmpty) {
val noDefaultValueFields = missingExcludeMappingName.filterNot(customDefaultValueMapping.keySet.contains)
if (noDefaultValueFields.nonEmpty) {
c.abort(
c.enclosingPosition,
s"From type: `$fromClassName` has fewer fields than To type: `$toClassName` and cannot be transformed!" +
s"\nMissing field mapping: `$fromClassName`.? => `$toClassName`.`${missingExcludeMappingName.mkString(",")}`." +
s"\nPlease consider using `setName` or `setDefaultValue` method for `$toClassName`.${missingExcludeMappingName
.mkString(",")}!"
)
}
}
val fields = toClassInfo.map { field =>
val fromFieldName = customFieldNameMapping.get(field.fieldName)
val realToFieldName = fromFieldName.fold(field.fieldName)(x => x)
......@@ -179,14 +203,14 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
case None if customFieldTypeMapping.contains(field.fieldName) =>
q"""${TermName(field.fieldName)} = ${TermName(builderFunctionPrefix + field.fieldName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
case _ =>
checkFieldGetFieldTerm[From](
checkForNoMappingField[From, To](
realToFieldName,
fromClassInfo.find(_.fieldName == realToFieldName),
field,
customDefaultValueMapping
customFieldNameMapping
)
}
}
}.filterNot(_ == EmptyTree)
q"""
${toClassName.toTermName}.apply(
..$fields
......@@ -194,37 +218,57 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
"""
}
private def checkFieldGetFieldTerm[From: WeakTypeTag](
private def checkForNoMappingField[From: WeakTypeTag, To: WeakTypeTag](
realFromFieldName: String,
fromFieldOpt: Option[FieldInformation],
toField: FieldInformation,
customDefaultValueMapping: mutable.Map[String, Any]
customFieldNameMapping: mutable.Map[String, String]
): Tree = {
val toClassInfo = getCaseClassFieldInfoList[To]()
val fromClassInfo = getCaseClassFieldInfoList[From]()
val customOptionsMapping =
MacroCache.transformerOptionsMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Set.empty)
val customDefaultValueMapping =
MacroCache.classFieldDefaultValueMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val fromFieldTerm = q"$fromTermName.${TermName(realFromFieldName)}"
val fromClassName = resolveClassTypeName[From]
fromFieldOpt match {
case Some(fromField) if !(fromField.fieldType weak_<:< toField.fieldType) =>
tryForWrapType(fromFieldTerm, fromField, toField)
tryForCollectionType(fromFieldTerm, fromField, toField)
case Some(fromField) if fromField.fieldType weak_<:< toField.fieldType =>
q"${TermName(toField.fieldName)} = $fromFieldTerm"
case None if !customDefaultValueMapping.keySet.contains(toField.fieldName) =>
c.abort(
c.enclosingPosition,
s"The value `$realFromFieldName` is not a member of `$fromClassName`!" +
s"\nPlease consider using `setDefaultValue` method!"
)
case _ =>
case None if customDefaultValueMapping.keySet.contains(toField.fieldName) =>
val value = q"""${TermName(builderDefaultValuePrefix$ + toField.fieldName)}"""
q"${TermName(toField.fieldName)} = $value"
case _ =>
val isStrictCollection = toField.collectionFlags.isStrictCollection && customOptionsMapping.contains(Options.enableCollectionDefaultsToEmpty)
val isOption = toField.collectionFlags.isOption && customOptionsMapping.contains(Options.enableOptionDefaultsToNone)
if (isStrictCollection || isOption) {
q"${TermName(toField.fieldName)} = ${getZeroValue(toField.fieldType)}"
} else {
if (!toField.hasDefaultValue) {
checkMissingFields[From, To](
fromClassInfo,
toClassInfo,
customDefaultValueMapping,
customFieldNameMapping,
customOptionsMapping
)
EmptyTree
} else {
EmptyTree
}
}
}
}
private def tryForWrapType(fromFieldTerm: Tree, fromField: FieldInformation, toField: FieldInformation): Tree =
private def tryForCollectionType(fromFieldTerm: Tree, fromField: FieldInformation, toField: FieldInformation): Tree =
(fromField, toField) match {
case (
FieldInformation(_, fromFieldType, collectionsFlags1, genericType1),
FieldInformation(_, toFieldType, collectionsFlags2, genericType2)
FieldInformation(_, fromFieldType, collectionsFlags1, genericType1, _, _),
FieldInformation(_, toFieldType, collectionsFlags2, genericType2, _, _)
)
if ((collectionsFlags1.isSeq && collectionsFlags2.isSeq) ||
(collectionsFlags1.isList && collectionsFlags2.isList) ||
......@@ -246,4 +290,36 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
q"""${TermName(toField.fieldName)} = $packageName.Transformer[${information1.fieldType}, ${information2.fieldType}].transform($fromFieldTerm)"""
}
private def checkMissingFields[From: WeakTypeTag, To: WeakTypeTag](
fromClassInfo: List[FieldInformation],
toClassInfo: List[FieldInformation],
customDefaultValueMapping: mutable.Map[String, Any],
customFieldNameMapping: mutable.Map[String, String],
customOptionsMapping: mutable.Set[Options]
) = {
val toClassName = resolveClassTypeName[To]
val fromClassName = resolveClassTypeName[From]
val missingFields = toClassInfo.filterNot(t => fromClassInfo.map(_.fieldName).contains(t.fieldName))
val missingExcludeMappingName = missingFields.filterNot(m => customFieldNameMapping.contains(m.fieldName))
if (missingExcludeMappingName.nonEmpty) {
val noDefaultValueFields = missingExcludeMappingName
.filterNot(m => customDefaultValueMapping.keySet.contains(m.fieldName))
.filterNot(_.hasDefaultValue)
if (noDefaultValueFields.nonEmpty) {
// scalafmt: { maxColumn = 400 }
val needHandleFields = (if (customOptionsMapping.contains(Options.enableOptionDefaultsToNone)) {
noDefaultValueFields.filterNot(_.collectionFlags.isOption)
} else if (customOptionsMapping.contains(Options.enableCollectionDefaultsToEmpty)) {
noDefaultValueFields.filterNot(_.collectionFlags.isStrictCollection)
} else {
noDefaultValueFields
}).map(_.fieldName)
c.abort(
c.enclosingPosition,
s"Missing field mapping: `$fromClassName`.? => `$toClassName`.[${needHandleFields.mkString(",")}]." +
s"\nPlease consider using `setName`、`setDefaultValue` or `enable*` methods for `$toClassName`.[${needHandleFields.mkString(",")}]!"
)
}
}
}
}
......@@ -384,4 +384,128 @@ class TransformableTest extends AnyFlatSpec with Matchers {
| .instance
|""".stripMargin shouldNot compile
}
"TransformableTest enable* method" should "ok" in {
case class A1(a: String, b: Int, cc: Long)
case class A2(
a: String,
b: Int,
c: Int,
d: Option[String],
e: List[String],
f: Seq[String],
g: Set[String],
h: Vector[String]
)
val a = A1("hello", 1, 2)
implicit val b: Transformer[A1, A2] = Transformable[A1, A2]
.setName(_.cc, _.c)
.setType[Long, Int](_.cc, fromField => fromField.toInt)
.enableOptionDefaultsToNone
.enableCollectionDefaultsToEmpty
.instance
a.transform[A2](b).toString shouldEqual "A2(hello,1,2,None,List(),List(),Set(),Vector())"
implicit val b2: Transformer[A1, A2] = Transformable[A1, A2]
.setName(_.cc, _.c)
.setType[Long, Int](_.cc, fromField => fromField.toInt)
.setDefaultValue[Vector[String]](
_.h,
Vector("Hello world")
) // Higher priority than enableCollectionDefaultsToEmpty
.enableCollectionDefaultsToEmpty
.enableOptionDefaultsToNone
.instance
a.transform[A2](b2).toString shouldEqual "A2(hello,1,2,None,List(),List(),Set(),Vector(Hello world))"
implicit val b3: Transformer[A1, A2] = Transformable[A1, A2]
.setName(_.cc, _.c)
.setType[Long, Int](_.cc, fromField => fromField.toInt)
.setDefaultValue[Option[String]](_.d, Option("Hello world")) // Higher priority than enableOptionDefaultsToNone
.enableCollectionDefaultsToEmpty
.instance
a.transform[A2](b3).toString shouldEqual "A2(hello,1,2,Some(Hello world),List(),List(),Set(),Vector())"
implicit val b4: Transformer[A1, A2] = Transformable[A1, A2]
.setName(_.cc, _.c)
.setType[Long, Int](_.cc, fromField => fromField.toInt)
.setDefaultValue[Vector[String]](
_.h,
Vector("Hello world1")
) // Higher priority than enableCollectionDefaultsToEmpty
.setDefaultValue[Option[String]](_.d, Option("Hello world2")) // Higher priority than enableOptionDefaultsToNone
.enableOptionDefaultsToNone
.enableCollectionDefaultsToEmpty
.instance
a.transform[A2](b4).toString shouldEqual "A2(hello,1,2,Some(Hello world2),List(),List(),Set(),Vector(Hello world1))"
}
"TransformableTest disable* method" should "ok" in {
case class A1(d: Option[String])
case class A2(
d: Option[String],
e: Option[String] = Some("option"),
f: Option[String] = None,
h: List[String] = List("list"),
i: List[String] = List.empty
)
val a = A1(Some("hello a"))
implicit val b1: Transformer[A1, A2] =
Transformable[A1, A2].enableCollectionDefaultsToEmpty.enableOptionDefaultsToNone.instance
a.transform[A2](b1).toString shouldEqual "A2(Some(hello a),None,None,List(),List())"
implicit val b2: Transformer[A1, A2] =
Transformable[A1, A2]
// This method has a higher priority
.setDefaultValue(_.f, Option("1"))
.disableCollectionDefaultsToEmpty // use default value, not None
.disableOptionDefaultsToNone // use default value, not Empty
.instance
a.transform[A2](b2).toString shouldEqual "A2(Some(hello a),Some(option),Some(1),List(list),List())"
}
"TransformableTest disable* is ok" should "compile ok" in {
case class A1(d: Option[String])
case class A2(
d: Option[String],
e: Option[String] = Some("option"),
f: Option[String] = None,
h: List[String] = List("list"),
i: List[String] = List.empty
)
val a = A1(Some("hello a"))
implicit val b1: Transformer[A1, A2] = Transformable[A1, A2].instance
a.transform[A2](b1).toString shouldEqual "A2(Some(hello a),Some(option),None,List(list),List())"
}
"TransformableTest disable* is ok if no default value" should "compile failed" in {
"""
| case class A1(d: Option[String])
| case class A2(
| d: Option[String],
| e: Option[String],
| f: Option[String] = None,
| h: List[String] = List("list"),
| i: List[String] = List.empty,
| )
|
| val a = A1(Some("hello a"))
| implicit val b1: Transformer[A1, A2] = Transformable[A1, A2].instance
|
| a.transform[A2](b1)
|""".stripMargin shouldNot compile
}
}
......@@ -21,8 +21,8 @@
package org.bitlap.csv.derive
import org.bitlap.common.internal.AbstractMacroProcessor
import scala.reflect.macros.blackbox
import org.bitlap.common.AbstractMacroProcessor
import org.bitlap.csv.{ Converter, CsvFormat }
/** This is a tool macro for automatic derivation of the base CSV converter.
......
......@@ -21,7 +21,8 @@
package org.bitlap.csv.macros
import org.bitlap.common.{ AbstractMacroProcessor, MacroCache }
import org.bitlap.common.MacroCache
import org.bitlap.common.internal.AbstractMacroProcessor
import org.bitlap.csv.{ CsvFormat, CsvableBuilder }
import java.io.File
......
......@@ -21,7 +21,8 @@
package org.bitlap.csv.macros
import org.bitlap.common.{ AbstractMacroProcessor, MacroCache }
import org.bitlap.common.MacroCache
import org.bitlap.common.internal.AbstractMacroProcessor
import org.bitlap.csv.{ CsvFormat, ScalableBuilder }
import java.io.InputStream
......@@ -160,7 +161,7 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa
// scalafmt: { maxColumn = 400 }
private def scalableBody[T: WeakTypeTag](clazzName: TypeName, innerFuncTermName: TermName): Tree = {
val customTrees = MacroCache.builderFunctionTrees.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val params = getCaseClassFieldInfo[T]()
val params = getCaseClassFieldInfoList[T]()
val fieldNames = params.map(_.fieldName)
val fields = checkGetFieldTreeInformationList[T](innerFuncTermName).map { fieldTreeInformation =>
val idx = fieldTreeInformation.index
......
......@@ -21,7 +21,7 @@
package org.bitlap.csv.macros
import org.bitlap.common.AbstractMacroProcessor
import org.bitlap.common.internal.AbstractMacroProcessor
import org.bitlap.csv.CsvFormat
import scala.reflect.macros.blackbox
......
......@@ -21,7 +21,7 @@
package org.bitlap.csv.macros
import org.bitlap.common.AbstractMacroProcessor
import org.bitlap.common.internal.AbstractMacroProcessor
import org.bitlap.csv.CsvFormat
import scala.reflect.macros.blackbox
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册