Skip to content

Commit

Permalink
support options (#210)
Browse files Browse the repository at this point in the history
* support options
  • Loading branch information
jxnu-liguobin authored Jun 28, 2022
1 parent a0165f6 commit 9179156
Show file tree
Hide file tree
Showing 15 changed files with 536 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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).
*/
Expand Down
63 changes: 2 additions & 61 deletions smt-common/src/main/scala/org/bitlap/common/CaseClassField.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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]
}
2 changes: 2 additions & 0 deletions smt-common/src/main/scala/org/bitlap/common/MacroCache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
40 changes: 40 additions & 0 deletions smt-common/src/main/scala/org/bitlap/common/Options.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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

}
41 changes: 35 additions & 6 deletions smt-common/src/main/scala/org/bitlap/common/Transformable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@
*/

package org.bitlap.common
import org.bitlap.common.internal.TransformerMacro

/** @author
* 梦境迷离
* @version 1.0,6/15/22
*/
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
Expand All @@ -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.
Expand All @@ -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]

}
Expand All @@ -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
Expand Down
Loading

0 comments on commit 9179156

Please sign in to comment.