Scala宏:检查Java字段是否标记为瞬态

tpu*_*der 5 scala scala-macros

如何使用Scala宏检查Java类字段上的ACC_TRANSIENT标志?

TermSymbol有像isPrivate和isProtected这样的方法,但没有任何类型的isTransient方法.

检查Scala的瞬态

对于Scala类,使用@transient注释生成带有ACC_TRANSIENT标志的字段:

class ScalaExample {
  @transient protected var ignoredField: String = null
}
Run Code Online (Sandbox Code Playgroud)

在类文件中,您最终得到:

  private transient java.lang.String ignoredField;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_TRANSIENT
Run Code Online (Sandbox Code Playgroud)

然后在宏中我可以看到@transient注释:

scala> typeOf[ScalaExample].member(TermName("ignoredField")).asTerm.accessed.annotations
res0: List[universe.Annotation] = List(scala.transient)
Run Code Online (Sandbox Code Playgroud)

检查瞬态是否为Java?

但是,如果我有一个Java类:

public class JavaExample {
  protected transient String ignoredField;
}
Run Code Online (Sandbox Code Playgroud)

哪个产生类似的字节码(该字段受保护而不是私有):

  protected transient java.lang.String ignoredField;
    descriptor: Ljava/lang/String;
    flags: ACC_PROTECTED, ACC_TRANSIENT
Run Code Online (Sandbox Code Playgroud)

没有注释:

scala> typeOf[JavaExample].member(TermName("ignoredField")).asTerm.accessed.annotations
java.lang.AssertionError: assertion failed: variable ignoredField
  at scala.reflect.internal.Symbols$Symbol.accessed(Symbols.scala:1978)
  at scala.reflect.internal.Symbols$Symbol.accessed(Symbols.scala:1974)
  at scala.reflect.internal.Symbols$TermSymbol.accessed(Symbols.scala:2658)
  ... 43 elided

scala> typeOf[JavaExample].member(TermName("ignoredField")).asTerm.annotations
res2: List[universe.Annotation] = List()
Run Code Online (Sandbox Code Playgroud)

我意识到,对于Scala类,@ transnsient注释可能正在从ScalaSignature中读取而不是ACC_TRANSIENT标志,这就是为什么它不会出现在java类中的原因.

可能的解决方法/黑客

使用Scala运行时反射我可以获取JavaExample的java.lang.Class,然后使用Java反射使用java.lang.reflect.Modifier.isTransient(...)检查ACC_TRANSIENT标志.但对于使用编译时间反射的宏来说,这似乎并不理想.我还没有弄清楚如何让它在宏中工作,所以我不确定它是否可能.

tpu*_*der 2

可能的解决方法/黑客解决方案

我最终实现了问题中提到的可能的解决方法/黑客:

import java.lang.reflect.Modifier
import java.net.URLClassLoader
import scala.reflect.macros._

abstract class MacroHelpers { self =>
  val ctx: Context
  import ctx.universe._

  private lazy val classLoader: ClassLoader = new URLClassLoader(ctx.classPath.toArray)

  def isJavaTransient(sym: Symbol): Boolean = {
    if (!sym.isJava || !sym.isTerm || !sym.asTerm.isVar) return false
    val className: String = sym.owner.asClass.fullName
    val clazz: Class[_] = classLoader.loadClass(className)  
    Modifier.isTransient(clazz.getDeclaredField(sym.name.decoded).getModifiers())
  }
}
Run Code Online (Sandbox Code Playgroud)

对于我使用 Scala 2.10.4 和 2.11.1 来说,这似乎工作得很好。如果您在混合 Scala/Java 项目中使用宏并尝试检查项目中的 Java 类,那么我认为您需要CompileOrder.JavaThenScala在 build.sbt 中确保 Java 类在宏运行之前显示在类路径上:

compileOrder := CompileOrder.JavaThenScala
Run Code Online (Sandbox Code Playgroud)