2012-07-13 14 views
9

he la siguiente anotación Java defineCómo acceder anotación definido en el campo de la clase en tiempo de ejecución caso

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

Y he la siguiente clase de caso Scala definido:

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

estoy poder ver la anotación definida en el nivel de clase de caso utilizando la reflexión, pero no puedo acceder a la anotación definida para arg2 miembro de la clase de caso TestAnno. Descompilando el código veo que ni la declaración de la variable ni su acceso scala parecen tener la anotación. Solo la definición de constructor y el método copy parecen conservar la anotación para los parámetros definidos en la declaración de clase de caso.

¿Hay alguna otra manera de forzar al compilador Scala para generar anotaciones para los campos declarados en la clase de caso o tengo que leer el constructor y el uso de una biblioteca como ASM ByteCode Library o ParaNamer para encontrar los parámetros que tienen que anotaciones? Necesita una solución que funcione principalmente para las clases de casos de Scala.

Respuesta

2

Hice algunas búsquedas y he venido con dos soluciones. Comentarios, sugerencias, mejoras son bienvenidas. He marcado esta respuesta como wiki. El primero se basa en ScalaBeans y 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 
    } 

El segundo enfoque utiliza ScalaSignatures y se basa en este 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 
    } 
9

sólo tiene que hacer lo siguiente:

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

Más información aquí http://www.scala-lang.org/api/current/#scala.annotation.meta.package

+0

Dependiendo de cómo se accede a estos parámetros por reflexión, es posible que tenga que añadir '@ setter', '@ getter', o' @ param' en lugar o junto con '@ field'. (Puedes probar con todos ellos y eliminarlos uno a uno.) – al3xar

5

Quentin solución funcionó, pero en mi humilde opinión es demasiado repetitivo para el usuario.

Puede leer las anotaciones sobre los argumentos del constructor con la API de reflexión estándar. Necesitaba esto para una implementación macro.

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

¿Cómo se puede acceder a la información de la anotación? – bashan

Cuestiones relacionadas