2014-04-13 1 views
8

TL; DR: В принципе, я ищу эквивалент Scala на Java:Доступ к аннотациям значения в Scala

(MyAnnotation) Thing.getClass().getAnnotations()[0] 

Несмотря на то, что я могу с радостью обнаружить аннотации и запрос на основе их типа, я не могу получить от scala.reflect.runtime.universe.Annotation в мой фактический тип.

scala> // Declare an annotation (it seems StaticAnnotation means runtime 
scala> // retention) 
scala> case class MyAnnotation(x: Int, y: String) extends scala.annotation.StaticAnnotation 
defined class MyAnnotation 

scala> // Make a thing decorated with MyAnnotation 
scala> @MyAnnotation(x=5, y="cool") case class Thing() 
defined class Thing 

scala> // Look at the annotation on the Thing...the runtime clearly 
scala> // understands the values on it 
scala> val annotation = scala.reflect.runtime.universe.typeOf[Thing].typeSymbol.asClass.annotations(0) 
annotation: reflect.runtime.universe.Annotation = MyAnnotation(5, "cool") 

scala> // I can sort of get at the values by index, which isn't terribly 
scala> // safe 
scala> annotation.scalaArgs(0) 
res0: reflect.runtime.universe.Tree = 5 

scala> // And what is a Tree here anyway? It certainly isn't a String (or 
scala> // Int). I just want the value! 
scala> annotation.scalaArgs(1) 
res1: reflect.runtime.universe.Tree = "cool" 

scala> // But how do I get at those values programatically? 
scala> annotation.asInstanceOf[MyAnnotation] 
java.lang.ClassCastException: scala.reflect.internal.AnnotationInfos$CompleteAnnotationInfo cannot be cast to MyAnnotation 
     at .<init>(<console>:13) 
     at .<clinit>(<console>) 
     at .<init>(<console>:7) 
     at .<clinit>(<console>) 
     at $print(<console>) 
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
     at java.lang.reflect.Method.invoke(Method.java:606) 
     at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:734) 
     at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:983) 
     at scala.tools.nsc.interpreter.IMain.loadAndRunReq$1(IMain.scala:573) 
     at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:604) 
     at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:568) 
     at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:760) 
     at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:805) 
     at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:717) 
     at scala.tools.nsc.interpreter.ILoop.processLine$1(ILoop.scala:581) 
     at scala.tools.nsc.interpreter.ILoop.innerLoop$1(ILoop.scala:588) 
     at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:591) 
     at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:882) 
     at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837) 
     at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837) 
     at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135) 
     at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:837) 
     at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83) 
     at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96) 
     at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105) 
     at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala) 

Радостная часть обо всем этом, я не могу даже использовать традиционный метод Java, даже если я хотел, так как Scala не удосужилась заполнить массив из getAnnotations() больше.

scala> Thing.getClass.getAnnotations.length 
res2: Int = 0 

эту же тему

Я думаю, что я хочу, это своего рода в "Reflection Overview" в разделе «инстанцировании типа во время выполнения», но я не понимаю, почему я должен прыгать через так много обручей, чтобы получить значения в аннотации. В Java значение просто отброшено.

Вопрос "See annotations in Scala reflection" кажется связанным, но вопрос с 2011 года относится к Scala 2.9. Я использую 2.10, и из того, что я могу сказать, с тех пор изменилось огромное количество отражений.

ответ

5

В их нынешней форме аннотации Scala стараются сочетать совместимость Java (что подразумевает только постоянные аргументы и очень ограниченное количество языковых конструкций, разрешенных в аннотациях) и предельную гибкость (что подразумевает разрешение всего, что можно было бы представить в аннотациях).

Это проявляется в различии ClassfileAnnotation (совместимость) и StaticAnnotation (гибкость). Согласно этой идее, можно выбирать между отражением в стиле Java с ограниченными аннотациями, доступными как объекты, и отражение в стиле Скала с полностью гибкими аннотациями, доступными только как абстрактные синтаксические деревья (обратите внимание, что мы не можем автоматически преобразовывать статические аннотации в объекты времени выполнения , потому что код, хранящийся в таких аннотациях, может содержать произвольные выражения Scala, что очень сложно их оценить).

К сожалению, эта идеалистическая картина нарушена тем фактом, что аннотации classfile не поддерживают задержку во время выполнения: https://issues.scala-lang.org/browse/SI-32, что означает, что на самом деле на самом деле нет выбора - только отражение в стиле Scala поддерживается на данный момент. Надеюсь, когда-нибудь мы получим SI-32, но я не знаю о каких-либо постоянных усилиях, чтобы заставить его работать.

Пару месяцев назад я хотел реализовать что-то вроде scala.reflect.Annotation.eval, которое бы взяло аннотацию стиля в стиле Скала и, если возможно, оценила ее. Однако после обсуждения на одном из наших совещаний по размышлению мы решили отказаться от него, потому что, помимо несчастливой неопределенности, этот API также принесет проблемы во временное отражение компиляции (которое в Scala унифицировано с отражением времени исполнения).

Это зарегистрировано как вопрос на https://issues.scala-lang.org/browse/SI-6423, а также обсуждено на https://groups.google.com/forum/#!topic/scala-internals/8v2UL-LR9yY, но без конкретных планов улучшения на данный момент. Надеюсь, проект Palladium поможет здесь, поскольку одним из его основных компонентов является интерпретатор Scala, который обеспечит необходимую общность для поддержки Annotation.eval.

+0

Спасибо за информацию. Я предполагаю, что обходным пути здесь является определение '@ interface' в Java и переход оттуда. Позор ... новые атрибуты Scala выглядят намного мощнее, чем их Java-аналоги. –

+0

Ну, если вам нравятся аннотации Scala больше, чем Java, возможно, вам будет интересно написать Annotation.eval самостоятельно? Это не должно быть очень сложным для случая постоянных аргументов. –

+0

Я бы с удовольствием, но для этого потребуется навык Scala, который превосходит мой собственный :-) –

6

Не эквивалент java, но с scala 2.11.6, это работает для извлечения значений из аннотации:

case class MyAnnotationClass(id: String) extends scala.annotation.StaticAnnotation 

val myAnnotatedClass: ClassSymbol = u.runtimeMirror(Thread.currentThread().getContextClassLoader).staticClass("MyAnnotatedClass") 
val annotation: Option[Annotation] = myAnnotatedClass.annotations.find(_.tree.tpe =:= u.typeOf[MyAnnotationClass]) 
val result = annotation.flatMap { a => 
    a.tree.children.tail.collect({ case Literal(Constant(id: String)) => DoSomething(id) }).headOption 
} 

Я понимаю, что вы могли бы сделать это уже, но только в том случае, это может помочь кому-то :)

+0

Спасибо за это решение. Не могли бы вы также предложить, как извлечь labmda вместо константы, а затем использовать ее для вычислений? –

+1

Что такое 'u'? Не могли бы вы привести пример? –

+0

спасибо, он работает, и если вам нужно только значение, вы можете изменить его так: val result = annotation.flatMap {a => a.tree.children.tail.head.collect ({case Literal (Константа (значение)) => значение}). headOption } lazy val max = result.get.value.asInstanceOf [Int] –

0

без того, чтобы зависеть от scala- компилятор, это моя версия:

def fetchAnnotations[T <: Annotation](cls : Class[_]) : List[T]= { 
    import scala.reflect.runtime.universe._ 
    val mirror = runtimeMirror(cls.getClassLoader) 
    val clsSymbol = mirror.staticClass(cls.getCanonicalName) 
    val annotations = clsSymbol.annotations 

    val res = ListBuffer[T]() 
    for(annt : Annotation <- annotations) { 
     val anntCls = annt.tree.tpe.typeSymbol.asClass 
     val classMirror = mirror.reflectClass(anntCls); 
     val anntType = annt.tree.tpe 
     val constructor = anntType.decl(termNames.CONSTRUCTOR).asMethod; 
     val constructorMirror = classMirror.reflectConstructor(constructor); 

     val instance = annt.tree match { 
     case Apply(c, args : List[Tree]) => 
      val res = args.collect({ 
      case i: Tree => 
       i match { 
       case Literal(Constant(value)) => 
        value 
       } 
      }) 
      constructorMirror(res: _*).asInstanceOf[T] 
     } 

     res+=(instance) 
    } 
    res.toList 
    } 

Предыдущий код будет работать, только если аргументы примитивны, я подозреваю.

Если мы можем себе позволить в зависимости от компилятора лестницы, то мы можем сделать что-то вроде:

def fetchAnnotations_toolBox[T <: Annotation](cls : Class[_]) : List[T]= { 
    import scala.reflect.runtime.universe._ 
    val mirror = runtimeMirror(cls.getClassLoader) 
    val clsSymbol = mirror.staticClass(cls.getCanonicalName) 
    val annotations = clsSymbol.annotations 

    val res = ListBuffer[T]() 
    for(annt : Annotation <- annotations) { 
     import scala.tools.reflect.ToolBox 

     val toolbox = mirror.mkToolBox() 
     val instance = toolbox.eval(toolbox.untypecheck(toolbox.typecheck(annt.tree))).asInstanceOf[T] 
     res+=(instance) 
    } 
    res.toList 
    } 
Смежные вопросы