5

We can use reflection to get method names as follows:

object Foo { def bar(name:String, age:Int) = {} } 
val foo = Foo.getClass
val methods = foo.getMethods.filter(_.getName.startsWith("b"))
methods.foreach(m => println(m.getName))

I now need to get the parameter types and names.

  • Are the parameter names stored in the byte-code? If answer is yes, how to access them?
  • If answer above is no, can we store the names somehow using annotations?
  • Can someone given an example to read the types, and how to use them. I am interested only in functions having String and/or Array[String] type parameters.

[EDIT:] Java version of the solution also ok.

[EDIT:] Annotations seems to be one way to do it. However, Scala annotation support is not that good. Related SO question.

Community
  • 1
  • 1
Jus12
  • 17,824
  • 28
  • 99
  • 157
  • To (1), I believe the answer is no, because Java classfiles don't store the names. (2) seems probably, but I don't know. For (3), remember that not actually the whole type, just the type constructor is stored, because of erasure. Again annotations might solve this, but maybe it would be better to try a system that does not use reflection? In my experience while reflection can be convenient sometimes, you'll end up regretting it (and rigth now it doesn't even sound convenient). – Owen Oct 03 '11 at 21:36
  • It seems that the names are stored in the bytecode because when I use a Scala jar from Java (without the Scala source), the Eclipse/Netbeans IDE code completion shows the names. – Jus12 Oct 03 '11 at 22:39
  • 2
    @Jus12 That's not a *requirement* of bytecode, though, and is likely due to the presence of debug symbols. `javap -l` will print out a symbol table if one exists; you can verify the difference by testing both `java` and `java -g:vars` to see the class file with and without symbol name info. – Dave Newton Oct 03 '11 at 23:07
  • I created it for other reasons but it covers the question too: https://gist.github.com/1257784 It should be noted that it works with trunk version of Scala compiler only, though. – Grzegorz Kossakowski Oct 03 '11 at 23:39

3 Answers3

6

I've not tried it, but http://paranamer.codehaus.org/ is designed for this task.

Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118
  • I like this one! It is all self-contained, not depending on asm; it copies only needed portion from it. – lyomi Dec 08 '14 at 15:26
4

Java's bytecode specification doesn't require the parameter names to be stored. However, they can sneak in via the debugging symbols (if the compiler was told to generate them). I know that the ASM bytecode library reads these symbols if they are present. See my answer to "How to get the parameter names of an object's constructors" for a Java example of finding constructor parameter names (in bytecode, constructors are just methods whose name is <init>).

Community
  • 1
  • 1
Adam Paynter
  • 46,244
  • 33
  • 149
  • 164
  • Thanks. I tweaked your answer to get it to work with Scala. Just need to figure out how to handle the `Type` class. – Jus12 Oct 06 '11 at 01:33
  • @Jus12: What trouble are you having with the `Type` class? – Adam Paynter Oct 06 '11 at 11:48
  • It was my mistake. I was using Java `Type` when instead I should have been using the library's `Type` class. I solved it. – Jus12 Oct 06 '11 at 11:52
0

If debugging info is present in the classes, it can be done as follows.

I am basically using Adam Paynter's answer and copy-pasting the code from here after slight edit to get it to work in Scala.

package test
import java.io.InputStream
import java.util.ArrayList
import scala.collection.JavaConversions._
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.LocalVariableNode
import org.objectweb.asm.tree.MethodNode

object Util {
  case class Param(paraName:String, paraType:Type)
  case class ScalaMethod(name:String, returnType:Type, params:List[Param])

  def main(args:Array[String]):Unit = {
    getMethods(scala.util.Random.getClass).foreach(printMethod _ )
    def printMethod(m:ScalaMethod) = {
      println (m.name+" => "+m.returnType.getClassName)
      m.params.foreach(p =>
        println (" "+ p.paraName+":"+p.paraType.getClassName))
    }
  }

  /**
   * extracts the names, parameter names and parameter types of all methods of c
   */
  def getMethods(c:Class[_]):List[ScalaMethod] = {
    val cl:ClassLoader = c.getClassLoader();
    val t:Type = Type.getType(c);
    val url:String = t.getInternalName() + ".class";
    val is:InputStream = cl.getResourceAsStream(url);
    if (is == null)
      throw new IllegalArgumentException("""The class loader cannot
                                         find the bytecode that defined the
                                         class (URL: " + url + ")""");
    val cn = new ClassNode();
    val cr = new ClassReader(is);
    cr.accept(cn, 0);
    is.close();
    val methods = cn.methods.asInstanceOf[java.util.List[MethodNode]];
    var mList:List[ScalaMethod] = Nil
    if (methods.size > 0) for (i <- 1 to methods.size) {
      val m:MethodNode = methods.get(i-1)
      val argTypes:Array[Type] = Type.getArgumentTypes(m.desc);
      val paraNames = new java.util.ArrayList[String](argTypes.length)
      val vars = m.localVariables.asInstanceOf[java.util.List[LocalVariableNode]];
      var pList:List[Param] = Nil
      if (argTypes.length > 0) for (i <- 0 to argTypes.length) {
          // The first local variable actually represents the "this" object
          paraNames.add(vars.get(i).name);
          pList = Param(paraNames.get(i-1), argTypes(i-1)) :: pList
      }
      mList = ScalaMethod(m.name, Type.getReturnType(m.desc), pList) :: mList
    }
    mList
  }
}
Community
  • 1
  • 1
Jus12
  • 17,824
  • 28
  • 99
  • 157