Domanda

I am playing with reflection to achieve a deep analysis on a trait. One of the things I would like to get is the initial value set to a member field. For example, in the trait:

trait A {
  val x: Int = 3
  val y: String = "y"
}

it would be nice to know 3 and "y". I have not found anything related to this task in the API, and due to the following output (generated by scalac -Xprint):

abstract trait A extends Object {
  <accessor> def com$hablapps$A$_setter_$x_=(x$1: Int): Unit;
  <accessor> def com$hablapps$A$_setter_$y_=(x$1: String): Unit;
  <stable> <accessor> def x(): Int;
  <stable> <accessor> def y(): String
};
abstract trait A$class extends  {
  def /*A$class*/$init$($this: com.hablapps.A): Unit = {
    $this.com$hablapps$A$_setter_$x_=(3);
    $this.com$hablapps$A$_setter_$y_=("y");
    ()
  }
}

I am afraid it is going to be quite hard to access them, since they are kept in the $init$ method's body. Is there any (easy) way to get those values with reflection?

È stato utile?

Soluzione

You have to disassemble the bytecode:

trait A { val x: Int = 3 }

public abstract class A$class extends java.lang.Object{
public static void $init$(A);
  Code:
   0:    aload_0
   1:    iconst_3
   2:    invokeinterface #12,  2; //InterfaceMethod A.A$_setter_$x_$eq:(I)V
   7:    return

See line 1--the only place the value exists is in the bytecode for the init method!

You cannot get to this any other way, since if you have

trait A { val x: Int = 3 }
trait B extends A { override val x = 7 }
class C extends B {}

you find that C extends A$_setter_$x_$eq to do nothing at all--making the A$class.$init$ call a no-op and rendering the value unretrievable.

Proof:

public class C extends java.lang.Object implements B,scala.ScalaObject{
public void A$_setter_$x_$eq(int);
  Code:
   0:    return

public void B$_setter_$x_$eq(int);
  Code:
   0:    aload_0
   1:    iload_1
   2:    putfield #11; //Field x:I
   5:    return

Altri suggerimenti

I doubt you can introspect to this point. That's not information about the type, but code. If you have the tree for the trait, you can find it out, but, otherwise, I doubt it.

You can use, however, class file parsers to investigate this further. I assume these would appear as constants for the class, which can be read. I'm not sure you could associate them with the variable, but...

I'm not much familiar with class file parsers, but I think a tool called "asm" does that.

You can use Java's Proxy to create an instance of the trait that collects values from all calls to the trait's ...$setter$... methods. Here's a hackish example:

object Reflection {
  def traitInits(clazz : Class[_]) : Map[String, Object] = {
    var cl = clazz.getClassLoader
    if (cl == null) {
      cl = ClassLoader.getSystemClassLoader
    }

    var init : Option[java.lang.reflect.Method] = None
    try {
      for (m <- cl.loadClass(clazz.getName + "$class").getMethods if init.isEmpty)
        if (m.getName == "$init$")
          init = Some(m)
    } catch {
      case e : Exception =>
    }

    if (init.isEmpty) return Map()

    var encodedToDecodedSetterNameMap = Map[String, String]()

    for (m <- clazz.getDeclaredMethods()) {
      val setterPrefix = clazz.getName.replace('.', '$') + "$_setter_$"
      val encoded = m.getName
      if (encoded.startsWith(setterPrefix)) {
        val decoded = encoded.substring(setterPrefix.length, encoded.length - 4)
        encodedToDecodedSetterNameMap += (encoded -> decoded)
      }
    }

    var result = Map[String, Object]()

    import java.lang.reflect.InvocationHandler
    import java.lang.reflect.Proxy

    init.get.invoke(null, Proxy.newProxyInstance(cl, Array[Class[_]](clazz),
      new InvocationHandler {
        def invoke(proxy : Object,
                   method : java.lang.reflect.Method,
                   args : Array[Object]) = {
          encodedToDecodedSetterNameMap.get(method.getName) match {
            case Some(decodedName) => result += (decodedName -> args(0))
            case _                 =>
          }
          null
        }
      }))

    result
  }                                               //> traitInits: (clazz: Class[_])Map[String,Object]

  trait A {
    val x : Int = 3
    val y : String = "y"
  }

  traitInits(classOf[A])                          //> res0: Map[String,Object] = Map(x -> 3, y -> y)
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top