views:

495

answers:

4

When I research a new library, I sometimes find it hard to locate the implementation of a method.

In Java, Metho#getDeclaringClass provides the class that declared a given method. So by iterating over Class#getMethods, I can find for each method, the class that declared it.

In Scala, traits are converted to Java interfaces and a class that extends a trait will implement the methods of the trait by forwarding them to a companion class defining these methods statically. This means, that Method#getDeclaringClass will return the class, not the trait:

scala> trait A { def foo = {println("hi")}}
defined trait A

scala> class B extends A
defined class B

scala> classOf[B].getMethods.find(_.getName() == "foo").get.getDeclaringClass
res3: java.lang.Class[_] = class B

What is the best way to work around this? Meaning, given a class, how can I get a List[(Method, Class)] where each tuple is a method and the trait/class it was declared in?

A: 

If your goal is actually "research[ing] a new library," the documentation gives you this information. Inherited methods (not overridden) are listed and linked (their names only) under the inherited class that defines them.

Is this not sufficient for the purposes of understanding the libary? Also, each documentation page includes a link to the source code.

Randall Schulz
you're assuming that every library is well documented. and besides, my question is about reflection in general, not "how can I research new libraries". that part was so that people won't start asking "why do you need such reflection for?".
IttayD
Ascertaining the things you ask about (and you did say in your very first sentence that you were doing research into new libraries) are available reliably and completely in the source code and hence in the Scaladoc HTML. Furthermore, given the nature of the relationship between Scala and the JVM, more information will always be available via the source than via the bytecode.
Randall Schulz
A: 

here's something that sort-of-works:

def methods(c: Class[_]): Array[String] = {
        val dm = try {
                val cls = if (c.isInterface) Class.forName(c.getName() + "$class") else c


                cls.getDeclaredMethods().map(m =>                            
                decode(c.getCanonicalName) + "#" + 
                decode(m.getName) + "(" + 
                {m.getParameterTypes.toList match {
                        case h :: tail => tail.map{(c: Class[_]) => decode(c.getCanonicalName)}.mkString(",")
                        case Nil => ""
                }} + "): " +    
                decode(m.getReturnType.getCanonicalName))
        } catch {case _ => Array[String]()}

        dm ++ c.getInterfaces.flatMap(methods(_))
}

    scala> trait A {def hi = {println("hi")}}
    scala> class B extends A
    scala> methods(classOf[B]).foreach(println(_))
    Main.$anon$1.B#$tag(): int
    Main.$anon$1.B#Main$$anon$A$$$outer(): Main.$anon$1
    Main.$anon$1.B#Main$$anon$B$$$outer(): Main.$anon$1
    Main.$anon$1.B#hi(): void
    Main.$anon$1.A#$init$(): void
    Main.$anon$1.A#hi(): void
    scala.ScalaObject#$tag(): int
    scala.ScalaObject#$init$(): void

You can see there's some filtering that can be done and maybe some conversions. The most annoying thing is that B has a decleration of 'hi', because it forwards the call to A$class#hi. However, this is indistinguishable from the case where B actually overrides 'hi' with its own implementation.

IttayD
+1  A: 

In Scala 2.8 you can use the ScalaSigParser to parse the scala specific byte code information.

This will be more stable than the byte code serialization format of scala traits, classes and methods.

import tools.scalap.scalax.rules.scalasig._
import scala.runtime._

val scalaSig = ScalaSigParser.parse(classOf[RichDouble]).get
val richDoubleSymbol = scalaSig.topLevelClasses.head
val methods = richDoubleSymbol.children filter ( _ match {
    case m : MethodSymbol => true
    case _ => false
})

methods foreach println
richDoubleSymbol.isTrait
ScalaSigParser.parse(classOf[Ordered[Any]]).get.topLevelClasses.head.isTrait

Prints:

scala> methods foreach println
MethodSymbol(x, owner=0, flags=20080004, info=23 ,None)
MethodSymbol(<init>, owner=0, flags=200, info=33 ,None)
[...]
MethodSymbol(isPosInfinity, owner=0, flags=200, info=117 ,None)
MethodSymbol(isNegInfinity, owner=0, flags=200, info=117 ,None)

scala> richDoubleSymbol.isTrait
res1: Boolean = false

scala> ScalaSigParser.parse(classOf[Ordered[Any]]).get.topLevelClasses.head.isTrait
res2: Boolean = true

I suppose following this road you can build a reflection API for Scala.

Thomas Jung
A: 

In the above-mentioned sample code, there is no decode method. It survives when it is possible to describe it what method it is.

khayashi