1

I have an instance of Java Method that represents a Scala function with a Scala annotation (that extends StaticAnnotation). I know I can use Method.getAnnotation(classOf[SomeJavaAnnotation]) to get a Java annotation, but this returns null for Scala annotations.

How can I get the Scala annotation from this? It seems I need to convert it to a Scala MethodSymbol first, but I don't see an obvious way to do that and I've only seen resources showing how to go the other way (from Scala MethodSymbol to Java Method).

Ian
  • 5,704
  • 6
  • 40
  • 72

1 Answers1

1

Scala annotations are invisible for Java reflection.

Either

  • use Java annotations (with runtime retention policy), then they will be visible for Java reflection, or

  • use Scala reflection, it can see Scala annotations.


This is how java.lang.reflect.Method can be converted into scala.reflect.runtime.universe.MethodSymbol

import java.lang.reflect.Method
import scala.reflect.runtime
import scala.reflect.runtime.universe._

def methodToMethodSymbol(method: Method): MethodSymbol = {
  val runtimeMirror = runtime.currentMirror
  val classSymbol = runtimeMirror.classSymbol(method.getDeclaringClass)
  classSymbol.typeSignature.decl(TermName(method.getName)).asMethod // (*)
}

If there are overloaded versions of the method, you'll have to replace the line (*) with

classSymbol.typeSignature.decl(TermName(method.getName)).alternatives.find(
  _.asMethod.paramLists.flatten.map(_.typeSignature.erasure.typeSymbol.asClass) == 
    method.getParameterTypes.map(runtimeMirror.classSymbol).toList
).get.asMethod

Another implementation:

def methodToMethodSymbol(method: Method): MethodSymbol = {
  val runtimeMirror = runtime.currentMirror
  val castedRuntimeMirror = runtimeMirror.asInstanceOf[{
    def methodToScala(meth: Method): scala.reflect.internal.Symbols#MethodSymbol
  }]

  castedRuntimeMirror.methodToScala(method).asInstanceOf[MethodSymbol]
}

Testing:

class myAnnotation extends StaticAnnotation

class MyClass {
  @myAnnotation
  def myMethod(i: Int): Unit = ()
}

val clazz = classOf[MyClass]
val method = clazz.getMethod("myMethod", classOf[Int])
  
val methodSymbol = methodToMethodSymbol(method) // method myMethod
methodSymbol.annotations // List(myAnnotation)

Just in case, here is reverse conversion of scala.reflect.runtime.universe.MethodSymbol into java.lang.reflect.Method

def methodSymbolToMethod(methodSymbol: MethodSymbol): Method = {
  val runtimeMirror = runtime.currentMirror
  val classSymbol = methodSymbol.owner.asClass
  val clazz = runtimeMirror.runtimeClass(classSymbol)
  val paramClasses = methodSymbol.paramLists.flatten.map(paramSymbol =>
    runtimeMirror.runtimeClass(paramSymbol.typeSignature.erasure)
  )
  clazz.getMethod(methodSymbol.name.toString, paramClasses: _*)
}

Another implementation:

def methodSymbolToMethod(methodSymbol: MethodSymbol): Method = {
  type InternalMethodSymbol = scala.reflect.internal.Symbols#MethodSymbol
  val runtimeMirror = runtime.currentMirror
  val castedRuntimeMirror = runtimeMirror.asInstanceOf[{
    def methodToJava(sym: InternalMethodSymbol): Method
  }]

  castedRuntimeMirror.methodToJava(
    methodSymbol.asInstanceOf[InternalMethodSymbol]
  )
}

Get a java.lang.reflect.Method from a reflect.runtime.universe.MethodSymbol

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Is it possible to perform Scala reflection on a Java `Method`? I only have access to the `Method` instance. – Ian Oct 15 '20 at 18:16
  • 1
    Thanks! I was able to get the annotation I wanted with `methodSymbol.annotations.find(_.tree.tpe == typeOf[MyAnnotation])` – Ian Oct 15 '20 at 19:14