Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Macros: Checking if a Java Field is marked as transient

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.

Checking Transient for Scala

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)

Checking transient for Java?

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.

Possible Workaround / Hack

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.

like image 975
tpunder Avatar asked Nov 01 '22 22:11

tpunder


1 Answers

Possible Workaround / Hack Solution

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
like image 128
tpunder Avatar answered Nov 15 '22 05:11

tpunder