为什么Scala Macro用于案例类复制失败?

时间:2016-11-30 18:46:26

标签: scala scala-macros

我有大约24个Case类,我需要通过在不支持连接的数据存储区中更改序列化之前的几个公共元素来以编程方式进行增强。由于case类没有为copy(...)构造函数定义的特性,我一直在尝试使用宏 - 作为我看过的基础 this post documenting a macro并提出这个宏:

当我尝试编译时,我得到以下内容:

element.val()

我用以下内容调用它:

import java.util.UUID

import org.joda.time.DateTime
import scala.language.experimental.macros


trait RecordIdentification {
  val receiverId: Option[String]
  val transmitterId: Option[String]
  val patientId: Option[UUID]
  val streamType: Option[String]
  val sequenceNumber: Option[Long]
  val postId: Option[UUID]
  val postedDateTime: Option[DateTime]
}

object WithRecordIdentification {
  import scala.reflect.macros.Context

  def withId[T, I](entity: T, id: I): T = macro withIdImpl[T, I]

  def withIdImpl[T: c.WeakTypeTag, I: c.WeakTypeTag](c: Context)(
    entity: c.Expr[T], id: c.Expr[I]
  ): c.Expr[T] = {
    import c.universe._

    val tree = reify(entity.splice).tree
    val copy = entity.actualType.member(newTermName("copy"))

    val params = copy match {
      case s: MethodSymbol if (s.paramss.nonEmpty) => s.paramss.head
      case _ => c.abort(c.enclosingPosition, "No eligible copy method!")
    }

    c.Expr[T](Apply(
      Select(tree, copy),
      AssignOrNamedArg(Ident("postId"), reify(id.splice).tree) ::
      AssignOrNamedArg(Ident("patientId"), reify(id.splice).tree) ::
      AssignOrNamedArg(Ident("receiverId"), reify(id.splice).tree) ::
      AssignOrNamedArg(Ident("transmitterId"), reify(id.splice).tree) ::
      AssignOrNamedArg(Ident("sequenceNumber"), reify(id.splice).tree) :: Nil
    ))
  }
}

但是我收到编译错误:

class GenericAnonymizer[A <: RecordIdentification]() extends Schema {
  def anonymize(dataPost: A, header: DaoDataPostHeader): A = WithRecordIdentification.withId(dataPost, header)
}

我不太确定如何更改宏以支持多个参数...任何圣人建议?

1 个答案:

答案 0 :(得分:1)

假设您有一组以下案例类,您希望在序列化之前对某些属性进行匿名处理。

case class MyRecordA(var receiverId: String, var y: Int)
case class MyRecordB(var transmitterId: Int, var y: Int)
case class MyRecordC(var patientId: UUID, var y: Int)
case class MyRecordD(var streamType: String, var y: Int)
case class MyRecordE(var sequenceNumber: String, var streamType: String, var y: Int)

您可以使用scala反射库在运行时改变实例的属性。您可以使用implicit anonymize方法实现自定义匿名/增强逻辑,Mutator可根据您的实施情况选择性地根据需要更改给定实例的字段。

import java.util.UUID
import scala.reflect.runtime.{universe => ru}

implicit def anonymize(field: String /* field name */, value: Any /* use current field value if reqd */): Option[Any] = field match {
    case "receiverId" => Option(value.toString.hashCode)
    case "transmitterId" => Option(22)
    case "patientId" => Option(UUID.randomUUID())
    case _ => None
}

implicit class Mutator[T: ru.TypeTag](i: T)(implicit c: scala.reflect.ClassTag[T], anonymize: (String, Any) => Option[Any]) {

    def mask = {
        val m = ru.runtimeMirror(i.getClass.getClassLoader)
        ru.typeOf[T].members.filter(!_.isMethod).foreach(s => {
            val fVal = m.reflect(i).reflectField(s.asTerm)
            anonymize(s.name.decoded.trim, fVal.get).foreach(fVal.set)
        })

        i
    }
}

现在,您可以在任何实例上调用屏蔽:

val maskedRecord = MyRecordC(UUID.randomUUID(), 2).mask