How to access Annotation defined on case class field at Runtime

前端 未结 3 446
逝去的感伤
逝去的感伤 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: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("", 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
      }
    

提交回复
热议问题