How can I check for the ACC_TRANSIENT flag on a field of a Java Class using a Scala Macro?
TermSymbol has methods like isPrivate & isProtected but doesn't have any sort of isTransient method.
For a Scala class you use the @transient annotation which generates a field with the ACC_TRANSIENT flag:
class ScalaExample {
@transient protected var ignoredField: String = null
}
In the classfile you end up with:
private transient java.lang.String ignoredField;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_TRANSIENT
And then in a Macro I can see the @transient annotation:
scala> typeOf[ScalaExample].member(TermName("ignoredField")).asTerm.accessed.annotations
res0: List[universe.Annotation] = List(scala.transient)
However if I have a Java Class:
public class JavaExample {
protected transient String ignoredField;
}
Which produces similar bytecode (the field is protected instead of private):
protected transient java.lang.String ignoredField;
descriptor: Ljava/lang/String;
flags: ACC_PROTECTED, ACC_TRANSIENT
There are no annotations:
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()
I realize that for the Scala class the @transient annotation is probably being read out of the ScalaSignature instead of the ACC_TRANSIENT flag which is why it doesn't show up for the java class.
Using Scala Runtime Reflection I can get the java.lang.Class for JavaExample and then use Java reflection to check for the ACC_TRANSIENT flag using java.lang.reflect.Modifier.isTransient(...). But this doesn't seem ideal for a Macro using Compile Time Reflection. I also haven't figured out how to get it working in a Macro yet so I'm not sure if its even possible.
I ended up implementing the Possible Workaround / Hack mentioned in the question:
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())
}
}
This seems to work fine for me using both Scala 2.10.4 and 2.11.1. If you are using your Macro in a mixed Scala/Java project and are trying to inspect the Java classes in the project then I think you need CompileOrder.JavaThenScala
in your build.sbt to make sure the Java classes show up on the classpath before the Macro runs:
compileOrder := CompileOrder.JavaThenScala
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With