How to access Annotation defined on case class field at Runtime

前端 未结 3 447
逝去的感伤
逝去的感伤 2021-01-04 10:52

I\'ve the following java Annotation defined

   @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.FIELD})
   @Retenti         


        
相关标签:
3条回答
  • 2021-01-04 11:32

    You just need to do the following :

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

    More info here http://www.scala-lang.org/api/current/#scala.annotation.meta.package

    0 讨论(0)
  • 2021-01-04 11:42

    I did some searching around and have come with with two solutions. Comments, Suggesstions, improvements are welcome, I've marked this answer as wiki. The first one is based on ScalaBeans and ParaNamer.

      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
      }
    

    The second approach uses ScalaSignatures and is based on this SO answer:

      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
      }
    
    0 讨论(0)
  • 2021-01-04 11:54

    Quentin's solution worked, but IMHO it's too much boilerplate for the user.

    You can read annotations on the constructor arguments with the standard reflection API. I needed this for a macro implementation.

    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))
    
    0 讨论(0)
提交回复
热议问题