12

我定义了以下java注释

   @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.FIELD})
   @Retention(RetentionPolicy.RUNTIME)
   public @interface MyAnnotation { 
     String value() default "";
   }

我定义了以下 scala 案例类:

@Prefer("xyz")
case class TestAnno(arg1 : String, @Prefer("abc") agr2 : String)

我可以看到使用反射在案例类级别定义的注释,但我无法访问为arg2case class 的成员定义的注释TestAnno。反编译代码我发现变量声明或它的 scala 访问器似乎都没有注释。只有构造函数定义和copy方法似乎保留了案例类声明中定义的参数注释。

是否有其他方法可以强制scala编译器为case类中声明的字段生成注释,或者我必须阅读构造函数并使用诸如ASM ByteCode LibraryParaNamer之类的库来查找哪些参数具有哪些注释?需要一个主要适用于 Scala 案例类的解决方案。

4

3 回答 3

17

您只需要执行以下操作:

case class TestAnno(arg1 : String, @(Prefer @field)("abc") agr2 : String)

更多信息在这里http://www.scala-lang.org/api/current/#scala.annotation.meta.package

于 2012-11-04T21:48:56.710 回答
8

Quentin 的解决方案奏效了,但恕我直言,这对用户来说太繁琐了。

您可以使用标准反射 API 读取构造函数参数的注释。我需要这个来实现宏。

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.annotation.StaticAnnotation
final class min(i: Long) extends StaticAnnotation

case class Foo(@min(1) c: String)
import scala.reflect.runtime.universe._
symbolOf[Foo].asClass.primaryConstructor.typeSignature.paramLists.head.head.annotations

// Exiting paste mode, now interpreting.

import scala.annotation.StaticAnnotation
defined class min
defined class Foo
import scala.reflect.runtime.universe._
res0: List[reflect.runtime.universe.Annotation] = List(min(1L))
于 2015-02-02T14:13:51.907 回答
2

我做了一些搜索,并提出了两种解决方案。欢迎发表评论、建议和改进,我已将此答案标记为 wiki。第一个基于ScalaBeansParaNamer

  def valNamesWithAnnotations[T <: AnyRef](obj : T)(implicit m : Manifest[T]) : List[(String, List[java.lang.annotation.Annotation])] = {
    val descriptor = descriptorOf(obj.getClass)

    val c = descriptor.beanType.erasure

    val constructor: Option[Constructor[_]] = {
      if (c.getConstructors().isEmpty) None
      else Some(c.getConstructors()(0).asInstanceOf[Constructor[_]])
    }

    val paranamer = new BytecodeReadingParanamer
    val ctorParameterNames = constructor.map(paranamer.lookupParameterNames(_)).getOrElse(scala.Array[String]()).toList
    val ctorParamAnnos = constructor.getOrElse(sys.error("Cannot find constructor entry for class " + c.getName)).getParameterAnnotations

    val builder = List.newBuilder[(String, List[java.lang.annotation.Annotation])]
    val paramIter = ctorParameterNames.iterator
    val annoIter = ctorParamAnnos.iterator

    while(paramIter.hasNext && annoIter.hasNext ) {
      builder += ((paramIter.next, annoIter.next.toList))
    }

    builder.result
  }

第二种方法使用 ScalaSignatures 并基于此SO 答案

  def valNamesWithAnnotations[C: ClassManifest] : List[(String, List[java.lang.annotation.Annotation])] = {
    val cls = classManifest[C].erasure
    val ctors = cls.getConstructors

    assert(ctors.size == 1, "Class " + cls.getName + " should have only one constructor")
    val sig = ScalaSigParser.parse(cls).getOrElse(sys.error("No ScalaSig for class " + cls.getName + ", make sure it is a top-level case class"))

    val classSymbol = sig.parseEntry(0).asInstanceOf[ClassSymbol]
    assert(classSymbol.isCase, "Class " + cls.getName + " is not a case class")

    val tableSize = sig.table.size
    val ctorIndex = (1 until tableSize).find { i =>
      sig.parseEntry(i) match {
        case m @ MethodSymbol(SymbolInfo("<init>", owner, _, _, _, _), _) => owner match {
          case sym: SymbolInfoSymbol if sym.index == 0 => true
          case _ => false
        }
        case _ => false
      }
    }.getOrElse(sys.error("Cannot find constructor entry in ScalaSig for class " + cls.getName))

    val paramsListBuilder = List.newBuilder[String]
    for (i <- (ctorIndex + 1) until tableSize) {
      sig.parseEntry(i) match {
        case MethodSymbol(SymbolInfo(name, owner, _, _, _, _), _) => owner match {
          case sym: SymbolInfoSymbol if sym.index == ctorIndex => paramsListBuilder += name
          case _ =>
        }
        case _ =>
      }
    }

    val paramAnnoArr = ctors(0).getParameterAnnotations
    val builder = List.newBuilder[(String, List[java.lang.annotation.Annotation])]

    val paramIter = paramsListBuilder.result.iterator
    val annoIter = paramAnnoArr.iterator

    while(paramIter.hasNext && annoIter.hasNext ) {
      builder += ((paramIter.next, annoIter.next.toList))
    }

    builder.result
  }
于 2012-07-14T04:29:50.160 回答