2012-07-13 7 views
9

Mam następujące Java Adnotacja określonaJak uzyskać dostęp do adnotacji zdefiniowany na walizce klasy w czasie wykonywania

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

I mam następujące klasy przypadek scala zdefiniowane:

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

jestem zobaczenie adnotacji zdefiniowanej na poziomie klasy przypadków za pomocą refleksji, ale nie mam dostępu do adnotacji zdefiniowanej dla użytkownika arg2 członka klasy sprawy TestAnno. Odkompilowanie kodu Widzę, że ani deklaracja zmiennej, ani jego akcesor scala nie mają adnotacji. Wydaje się, że tylko definicja konstruktora i metoda copy zachowują adnotację dla parametrów zdefiniowanych w deklaracji klasy sprawy.

Czy istnieje jakiś inny sposób zmusić kompilator scala wygenerować adnotacji na obszarach zadeklarowanych w klasie wypadku lub muszę czytać konstruktora i korzystać z biblioteki takie jak ASM ByteCode Library lub ParaNamer dowiedzieć się, jakie parametry mają które adnotacje? Potrzebujesz rozwiązania, które będzie działać przede wszystkim dla klas przypadków Scala.

Odpowiedz

2

Zrobiłem kilka wyszukiwania i przyszedł z dwoma rozwiązaniami. Komentarze, sugestie, poprawki są mile widziane, zaznaczyłem tę odpowiedź jako wiki. Pierwszy oparty jest na ScalaBeans i 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 
    } 

Drugie podejście wykorzystuje ScalaSignatures i opiera się na tym 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

Wystarczy wykonać następujące czynności:

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

Więcej informacji tutaj http://www.scala-lang.org/api/current/#scala.annotation.meta.package

+0

W zależności od tego, w jaki sposób uzyskujesz dostęp do tych parametrów przez odbicie, może być konieczne dodanie '@ setter',' @ getter' lub '@ param' zamiast lub wraz z' @ field'. (Możesz wypróbować je wszystkie i usunąć jeden po drugim.) – al3xar

5

Quentina rozwiązanie zadziałało, ale IMHO jest zbyt wielkim zestawem dla użytkownik.

można odczytać adnotacje na argumentach Konstruktor ze standardowym refleksji API. Potrzebowałem tego do wdrożenia makr.

+0

W jaki sposób można uzyskać dostęp do informacji o adnotacjach? – bashan

Powiązane problemy