CLOVER🍀

That was when it all began.

Scalaでアノテーションの引数にアノテーションを指定する

出典、コップ本第2版P.552。

Scalaでは、アノテーションの引数に直接アノテーションを書くことができません。例えば、以下のようなアノテーションを用意して
src/main/java/Complex.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Complex {
    Value[] value();
}

src/main/java/Value.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Value {
    String value() default "v";

    String val1() default "v1";

    String val2() default "v2";
}

こんな感じに使おうとすると、
src/main/scala/SampleClass.scala

@Complex(Array(@Value))
class SampleClass {
}

コンパイルに失敗します。

[error] /xxxxx/src/main/scala/SampleClass.scala:3: illegal start of simple expression
[error] @Complex(Array(@Value))
[error]                ^

単純な式しか、そこは指定できませんよ、と。

これを回避するには、ちょっと奇妙ですがアノテーションをnewで指定します。

@Complex(Array(new Value))
class SampleClass {
}

引数がある場合には、コンストラクタでキーワード引数として指定します。

@Complex(Array(new Value("simple-value"),
               new Value(val1 = "one", val2 = "two")))
class SampleClass {
}

何も名前を与えないと、Javaと同様「value」を指定したものとして扱われるみたいです。

ちゃんと値も取れますよ、ということで。

import org.scalatest.FunSpec
import org.scalatest.Matchers._

class SampleClassSpec extends FunSpec {
  describe("nested annotation") {
    it("get annotated value") {
      val comp = classOf[SampleClass].getAnnotation(classOf[Complex])

      val values = comp.value
      values should have size 2

      val v1 = values(0)
      v1.value should be ("simple-value")

      val v2 = values(1)
      v2.val1 should be ("one")
      v2.val2 should be ("two")
    }
  }
}

Scalaを使っているとあんまりバシバシアノテーションを使わない気もするのですが、Java EEと合わせて使ってたりするとたまーにこういうのに遭遇します。

例えば、NamedQueriesなど。
*こちらはJavaコードです

@NamedQueries({
    @NamedQuery(name="Country.findAll",
                query="SELECT c FROM Country c"),
    @NamedQuery(name="Country.findByName",
                query="SELECT c FROM Country c WHERE c.name = :name"),
})

まあ、ぶつかった時は忘れていたわけですが…。