#Pluggable Annotation Processing API とは
Java 1.6 から追加された、コンパイル時にアノテーションを処理するための仕組み。
Lombok とか JPA の Metamodel とかで利用されている。
つまり、コンパイル時にアノテーションを読み込んでソースコードを自動生成したりできる。
Java 1.5 でアノテーションが追加されたときに、同様にアノテーションをコンパイル時に処理する仕組みとして Annotation Processing Tool (apt) が追加されていたが、これとは別物らしい。
#Hello World
##プロセッサーを実装する
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import javax.lang.model.SourceVersion;
import java.util.Set;
import java.util.HashSet;
public class MyAnnotationProcessor extends AbstractProcessor {
private int round = 1;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
System.out.println("Round : " + (this.round++));
annotations.forEach(System.out::println);
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add("*");
return supportedAnnotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
}
##処理対象のソースを作る
public class Hoge {
@Deprecated
public void deprecatedMethod() {}
@Override
public String toString() {
return "hoge";
}
}
##プロセッサーをコンパイルする
> javac MyAnnotationProcessor.java
> dir /b
MyAnnotationProcessor.class
MyAnnotationProcessor.java
Hoge.java
##プロセッサーを指定してコードをコンパイルする
> javac -processor MyAnnotationProcessor Hoge.java
Round : 1
java.lang.Deprecated
java.lang.Override
Round : 2
> dir /b
Hoge.class
Hoge.java
MyAnnotationProcessor.class
MyAnnotationProcessor.java
##説明
###プロセッサーの作成
import javax.annotation.processing.AbstractProcessor;
public class MyAnnotationProcessor extends AbstractProcessor {
- アノテーションプロセッサーを作るには、 Processor を実装したクラスを用意する。
- 標準で
Processor
を実装した抽象クラス AbstractProcessor が用意されているので、普通はそれを継承して作る。
###処理対象を定義する
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add("*");
return supportedAnnotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
-
getSupportedAnnotationTypes()
で、処理するアノテーションを定義する。- 戻り値の
Set<String>
では、処理対象となるアノテーションのパターンを返すようにする。 - 通常は、 FQCN を指定する。
-
*
を使ったワイルドカード表現も可能。 -
*
だけの場合は、全てのアノテーションが処理対象になる。
- 戻り値の
-
getSupportedSourceVersion()
は、サポート限界となるバージョンを定義する。- SourceVersion 列挙型で指定する。
- サポートバージョンより新しい JDK を使うと、警告メッセージが表示されるようになる。
警告: 注釈プロセッサ'MyAnnotationProcessor'から-source '1.8'より小さいソース・バージョン'RELEASE_7'がサポートされています
###コンパイル時にプロセッサーを指定する
> javac -processor MyAnnotationProcessor Hoge.java
-
-processor
オプションで、使用するプロセッサーを指定する。
###ラウンド
Round : 1
java.lang.Deprecated
java.lang.Override
Round : 2
- プロセッサーによる処理(
process
)の呼び出しは、複数回行われる。 - それぞれの処理を ラウンド と呼ぶ。
- 1回のラウンドの処理でソースコードを新規に生成したりすると、生成されたソースに使用されているアノテーションを処理するため、もう一度ラウンドが実行される。
- ソースの生成などをしなくても、最低2回はラウンドが実行される。
#サポートバージョンと処理対象の絞り込みをアノテーションで指定する
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.SourceVersion;
import java.util.Set;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
public class MyAnnotationProcessor extends AbstractProcessor {
private int round = 1;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
System.out.println("Round : " + (this.round++));
annotations.forEach(System.out::println);
return true;
}
}
- メソッドの変わりに、
@SupportedSourceVersion
と@SupportedAnnotationTypes
アノテーションで定義することができる。
#アノテーションの情報を取得する
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.Element;
import javax.lang.model.SourceVersion;
import java.util.Set;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment env) {
for (TypeElement typeElement : typeElements) {
for (Element element : env.getElementsAnnotatedWith(typeElement)) {
Override override = element.getAnnotation(Override.class);
if (override != null) {
System.out.println("@Override at " + element);
}
}
}
return true;
}
}
> javac -processor MyAnnotationProcessor Hoge.java
@Override at toString()
-
RoundEnvironment#getElementsAnnotatedWith(TypeElement)
メソッドで、実際にアノテートされている場所を表すElement
のSet
を取得できる。 -
Element#getAnnotation(Class)
メソッドで、実際のアノテーションを取得できる。
#メッセージ出力
##基本
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
messager.printMessage(Kind.OTHER, "Other");
messager.printMessage(Kind.NOTE, "Note");
messager.printMessage(Kind.WARNING, "Warning");
messager.printMessage(Kind.MANDATORY_WARNING, "Mandatory Warning");
messager.printMessage(Kind.ERROR, "Error");
return true;
}
}
注意:Other
注意:Note
警告: Warning
警告: Mandatory Warning
エラー: Error
注意:Other
注意:Note
警告: Warning
警告: Mandatory Warning
エラー: Error
エラー2個
警告4個
- ※2回ずつ出力されているのは、ラウンドが2回実行されているため。
-
AbstractProcessor
クラスが持つprocessingEnv
というフィールドから、getMessager()
メソッドを使って Messager のインスタンスを取得する。 -
Messager#printMessage()
メソッドを使って、コンパイル時のメッセージ出力ができる。 - 第一引数にメッセージの種類を指定する(Diagnostic.Kind)。
- 第二引数に出力するメッセージを渡す。
- 種類を
Kind.ERROR
にしてメッセージを出力すると、コンパイル結果がエラーになる。
##アノテートされたコード上の位置情報を出力する
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
messager.printMessage(Kind.NOTE, "hogehoge", element);
}
}
return true;
}
}
Hoge.java:5: 注意:hogehoge
public String toString() {
^
-
Messager#printMessage()
メソッドの第三引数にElement
を渡すと、その要素のコード上の位置情報が一緒に出力される。
##アノテーションの位置情報を出力する
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("java.lang.Override")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
AnnotationMirror override = element.getAnnotationMirrors().get(0);
messager.printMessage(Kind.NOTE, "hogehoge", element, override);
}
}
return true;
}
}
Hoge.java:4: 注意:hogehoge
@Override
^
-
Messager#printMessage()
メソッドの第四引数にAnnotationMirror
を渡すと、そのアノテーションのコード上の位置情報が一緒に出力される。 - 第五引数に
AnnotationValue
を渡せば、アノテーションの引数の位置情報も出力できそう(試してない)。
#アノテートされている要素の情報を取得する
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({
ElementType.TYPE, ElementType.METHOD, ElementType.LOCAL_VARIABLE,
ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE_PARAMETER
})
public @interface MyAnnotation {
String value();
}
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
MyAnnotation anno = element.getAnnotation(MyAnnotation.class);
String msg =
"<<@MyAnnotation(\"" + anno.value() + "\")>>\n"
+ " Kind : " + element.getKind() + "\n"
+ " SimpleName : " + element.getSimpleName() + "\n"
+ " Modifiers : " + element.getModifiers() + "\n"
+ " asType : " + element.asType() + "\n"
+ " EnclosedElements : " + element.getEnclosedElements() + "\n"
+ " EnclosingElement : " + element.getEnclosingElement() + "\n"
+ " AnnotationMirrors : " + element.getAnnotationMirrors() + "\n"
;
System.out.println(msg);
}
}
return true;
}
}
@MyAnnotation("クラス")
public class Hoge<@MyAnnotation("型引数") T> {
@MyAnnotation("フィールド")
private double d;
@MyAnnotation("メソッド")
public void method(@MyAnnotation("引数") int i) {
@MyAnnotation("ローカル変数")
String str;
}
}
<<@MyAnnotation("クラス")>>
Kind : CLASS
SimpleName : Hoge
Modifiers : [public]
asType : Hoge<T>
EnclosedElements : Hoge(),d,method(int)
EnclosingElement : 名前のないパッケージ
AnnotationMirrors : @MyAnnotation("\u30af\u30e9\u30b9")
<<@MyAnnotation("型引数")>>
Kind : TYPE_PARAMETER
SimpleName : T
Modifiers : []
asType : T
EnclosedElements :
EnclosingElement : Hoge
AnnotationMirrors : @MyAnnotation("\u578b\u5f15\u6570")
<<@MyAnnotation("フィールド")>>
Kind : FIELD
SimpleName : d
Modifiers : [private]
asType : double
EnclosedElements :
EnclosingElement : Hoge
AnnotationMirrors : @MyAnnotation("\u30d5\u30a3\u30fc\u30eb\u30c9")
<<@MyAnnotation("メソッド")>>
Kind : METHOD
SimpleName : method
Modifiers : [public]
asType : (int)void
EnclosedElements :
EnclosingElement : Hoge
AnnotationMirrors : @MyAnnotation("\u30e1\u30bd\u30c3\u30c9")
<<@MyAnnotation("引数")>>
Kind : PARAMETER
SimpleName : i
Modifiers : []
asType : int
EnclosedElements :
EnclosingElement : method(int)
AnnotationMirrors : @MyAnnotation("\u5f15\u6570")
- Element インスタンスから、アノテートされた要素の情報をいろいろ取得できる。
#jar にパッケージして -processor オプション無しでも使えるようにする
毎回 javac
のオプションで -processor
を指定するのは疲れる。
アノテーションプロセッサーを所定の方法で jar にパッケージすると、コンパイル時に jar をクラスパスに指定するだけでプロセッサーの処理を挟むことができるようになる。
##プロセッサーを含む jar を作成する
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("java.lang.Override")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyOverrideProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("Override!!");
return true;
}
}
@Override
アノテーションを処理するプロセッサー。
これを、以下の構成になるように jar にパッケージする。
|-sample/processor/
| `-MyOverrideProcessor.class
`-META-INF/services/
`-javax.annotation.processing.Processor
この META-INF/services/javax.annotation.processing.Processor
はただのテキストファイルで、内容は以下のように使用するプロセッサーの FQCN を記述しておく。
sample.processor.MyOverrideProcessor
この jar を仮に processor.jar
とする。
##実行する
public class Hoge {
@Override
public String toString() {
return super.toString();
}
}
> javac -cp processor.jar Hoge.java
Override!!
Override!!
##説明
- アノテーションプロセッサーを含む jar ファイルの中に、
META-INF/services/javax.annotation.processing.Processor
という形でテキストファイルを配置する。 -
javax.annotation.processing.Processor
の中には、使用するプロセッサーの FQCN を記述する。 - この jar をクラスパスに含めた状態でコンパイルを実行すると、
javax.annotation.processing.Processor
で指定されたプロセッサーがコンパイル時に利用される。 -
javax.annotation.processing.Processor
には、改行区切りで複数のプロセッサーを記述することができる。 - この
META-INF/services
の下に使用するクラスの情報を配置するという方法は、 Java の ServiceLoader という仕組みを利用したものと思われる。
#Eclipse で利用する
##基本
###プロセッサー側のプロジェクトを作成する
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
messager.printMessage(Kind.NOTE, "Hello Annotation Processor!!");
return true;
}
}
sample.processor.MyAnnotationProcessor
- 普通に java プロジェクトを作る。
- ServiceLoader を利用した形式にする(
javax.annotation.processing.Processor
)。
####jar ファイルを出力する
- プロジェクトを右クリックして、 [エクスポート] を選択する。
- [Java] > [JAR ファイル] を選択し、 [次へ] をクリック。
- 任意の場所に jar ファイルを出力する。
public class Hoge {
@Override
public String toString() {
return super.toString();
}
}
- 普通に java プロジェクトを作成する。
####注釈処理を有効にする
- プロジェクトを右クリックして、 [プロパティー] を選択。
- [Java コンパイラー] > [注釈処理] を選択。
- [プロジェクト固有の設定を可能にする] にチェックを入れる。
- 続いて、 [注釈処理を使用可能にする] と [エディターでの処理を使用可能にする] のチェックを入れる。
- さらに、 [注釈処理] > [ファクトリー・パス] を選択する。
- [プロジェクト固有の設定を可能にする] にチェックを入れる。
- [外部 Jar 追加] をクリックし、先ほど生成した
MyAnnotationProcessor
プロジェクトの jar ファイルを選択する。
これで、注釈処理が有効になる。
####出力結果を確認する
- [ウィンドウ] > [ビューの表示] > [エラー・ログ] を選択し、「エラー・ログ」ウィンドウを表示させる。
- うまく処理されていると、
Messager
で出力した内容が表示されている。 - Eclipse で動かす場合、標準出力(
System.out
)で出力した内容は確認できないので、Messager
で出力して「エラー・ログ」ウィンドウで確認する。 - プロセッサー側の実装を修正した場合は、 jar を出力しなおした上で利用側の「ファクトリー・パス」の設定をし直す(一回外して、再度設定し直す)必要がある。
##警告やエラーのメッセージはエディター上に表示される
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("java.lang.Override")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
messager.printMessage(Kind.ERROR, "オーバーライドさせぬ!", element);
}
}
return true;
}
}
利用側の様子
エディター上でエラー表示されてる!
#Gradle で使用する
##基本
###フォルダ構成
|-settings.gradle
|-build.gradle
|-processor/
| `-src/main/
| |-java/sample/processor/
| | `-MyAnnotationProcessor.java
| `-resources/META-INF/services/
| `-javax.annotation.processing.Processor
|
`-client/
|-build.gradle
`-src/main/java/sample/processor/
`-Hoge.java
- マルチプロジェクトの構成。
-
processor
とclient
というプロジェクトがある。-
processor
プロジェクトでは、アノテーションプロセッサーを実装する。 -
client
プロジェクトでは、processor
プロジェクトで作成したアノテーションプロセッサーを利用する。
-
###実装等
include 'processor', 'client'
subprojects {
apply plugin: 'java'
}
dependencies {
compile project(':processor')
}
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("Hello!!");
return true;
}
}
package sample.processor;
public class Hoge {
@Override
public String toString() {
return "hoge";
}
}
###動作確認
> gradle compileJava
:processor:compileJava
:processor:processResources
:processor:classes
:processor:jar
:client:compileJava
Hello!!
Hello!!
BUILD SUCCESSFUL
Total time: 3.423 secs
- ServiceLoader を使う形式にしておけば、普通に利用できた。
##Eclipse プロジェクト化
Gradle プラグイン作りました
2016/01/08 追記
以下の設定ファイルを自動生成する Gradle プラグインを作りました。
gradle eclipse
したら、自動で注釈処理の設定が有効になるようにする。
###フォルダ構成
|-build.gradle
|-lib/
| `-processor.jar
`-src/main/java/sample/processor/
`-Hoge.java
プロセッサーをパッケージングした jar ファイルは、 lib
フォルダの下の配置しておく。
###build.gradle
apply plugin: 'java'
apply plugin: 'eclipse'
ext {
eclipseAptPrefsFile = '.settings/org.eclipse.jdt.apt.core.prefs'
eclipseFactoryPathFile = '.factorypath'
processorJarPath = 'lib/processor.jar'
}
dependencies {
compile files(processorJarPath)
}
eclipse {
project.name = 'MyAnnotationProcessorClient'
classpath.file.withXml {
it.asNode().appendNode('classpathentry', [kind: 'src', path: '.apt_generated'])
}
jdt.file.withProperties { properties ->
properties.put 'org.eclipse.jdt.core.compiler.processAnnotations', 'enabled'
}
}
eclipseJdt << {
file(eclipseAptPrefsFile).write """\
|eclipse.preferences.version=1
|org.eclipse.jdt.apt.aptEnabled=true
|org.eclipse.jdt.apt.genSrcDir=.apt_generated
|org.eclipse.jdt.apt.reconcileEnabled=true
|""".stripMargin()
file(eclipseFactoryPathFile).write """\
|<factorypath>
| <factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
| <factorypathentry kind="EXTJAR" id="${file(processorJarPath).absolutePath}" enabled="true" runInBatchMode="false"/>
|</factorypath>
|""".stripMargin()
}
cleanEclipse << {
file(eclipseAptPrefsFile).delete()
file(eclipseFactoryPathFile).delete()
}
###説明
やっていることは単純で、 eclipse
タスクを実行したら注釈処理を有効化するのに必要となる設定ファイルを追加したりしている。
org.eclipse.jdt.apt.core.prefs の作成
ext {
eclipseAptPrefsFile = '.settings/org.eclipse.jdt.apt.core.prefs'
...
}
...
eclipseJdt << {
file(eclipseAptPrefsFile).write """\
|eclipse.preferences.version=1
|org.eclipse.jdt.apt.aptEnabled=true
|org.eclipse.jdt.apt.genSrcDir=.apt_generated
|org.eclipse.jdt.apt.reconcileEnabled=true
|""".stripMargin()
...
.factorypath の作成
ext {
eclipseFactoryPathFile = '.factorypath'
...
}
...
eclipseJdt << {
...
file(eclipseFactoryPathFile).write """\
|<factorypath>
| <factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
| <factorypathentry kind="EXTJAR" id="${file(processorJarPath).absolutePath}" enabled="true" runInBatchMode="false"/>
|</factorypath>
|""".stripMargin()
}
.classpath の編集
eclipse {
...
classpath.file.withXml {
it.asNode().appendNode('classpathentry', [kind: 'src', path: '.apt_generated'])
}
...
}
org.eclipse.jdt.core.prefs の編集
eclipse {
...
jdt.file.withProperties { properties ->
properties.put 'org.eclipse.jdt.core.compiler.processAnnotations', 'enabled'
}
}
cleanEclipse に削除対象を追加
ext {
eclipseAptPrefsFile = '.settings/org.eclipse.jdt.apt.core.prefs'
eclipseFactoryPathFile = '.factorypath'
...
}
...
cleanEclipse << {
file(eclipseAptPrefsFile).delete()
file(eclipseFactoryPathFile).delete()
}
#Java のソースコードを生成する
アノテーションプロセッサーの処理の中で、ソースコードを動的に生成してみる。
##実装
package sample.processor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface MyAnnotation {
boolean value() default false;
}
package sample.processor;
@MyAnnotation(true)
public class Hoge {
}
package sample.processor;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
@SupportedAnnotationTypes("sample.processor.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.size() == 0) {
return true;
}
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
MyAnnotation anno = element.getAnnotation(MyAnnotation.class);
messager.printMessage(Kind.NOTE, element.getSimpleName() + " class is annotated by @MyAnnotation.");
if (!anno.value()) {
messager.printMessage(Kind.NOTE, "@MyAnnotation value is false. no generate.");
break;
}
String src =
"package sample.processor.generated;\r\n"
+ "import sample.processor.MyAnnotation;\r\n"
+ "@MyAnnotation\r\n"
+ "public class Fuga {\r\n"
+ " public void hello() {\r\n"
+ " System.out.println(\"Hello World!!\");\r\n"
+ " }\r\n"
+ "}\r\n"
;
try {
Filer filer = super.processingEnv.getFiler();
JavaFileObject javaFile = filer.createSourceFile("Fuga");
try (Writer writer = javaFile.openWriter()) {
writer.write(src);
}
messager.printMessage(Kind.NOTE, "generate source code!!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
}
##動作確認
注意:Hoge class is annotated by @MyAnnotation.
注意:generate source code!!
注意:Fuga class is annotated by @MyAnnotation.
注意:@MyAnnotation value is false. no generate.
package sample.processor.generated;
import sample.processor.MyAnnotation;
@MyAnnotation
public class Fuga {
public void hello() {
System.out.println("Hello World!!");
}
}
-
Fuga.java
は、.class
ファイルが出力されるフォルダのルートに出力された。 -
Fuga.class
は、同じく.class
ファイルの出力先の中に、package
宣言で定義した通りの場所に出力された。
|-Fuga.java
`-sample/processor/
|-Hoge.class
`-generated/
`-Fuga.class
##説明
try {
Filer filer = super.processingEnv.getFiler();
JavaFileObject javaFile = filer.createSourceFile("Fuga");
try (Writer writer = javaFile.openWriter()) {
writer.write(src);
}
messager.printMessage(Kind.NOTE, "generate source code!!");
} catch (IOException e) {
e.printStackTrace();
}
- Java のソースコードを生成するには、まず次の方法で JavaFileObject を取得する。
-
AbstractProcessor#processingEnv
のgetFiler()
メソッドで Filer のインスタンスを取得する。 -
Filer#createSourceFile(String)
メソッドで、JavaFileObject
のインスタンスを取得する(引数は生成するクラスの名前)。
-
-
JavaFileObject
を取得したら、openWriter()
やopenOutputStream()
を使ってライターやストリームを取得し、そこにソースコードを書き出す。 - 生成されたファイルは自動的にコンパイルされる。
- 生成したソースコードにアノテーションが含まれていた場合は、次のラウンドが実行され、再びプロセッサーの
process()
メソッドが実行される。 - 同じクラスのソースコードを複数回出力するとエラーになる。
- このため、処理すべきアノテーションが無くなったかどうかを確認して、なければすぐに処理を終了させている(判定には、
process()
メソッドの第一引数のサイズを利用する)。
- このため、処理すべきアノテーションが無くなったかどうかを確認して、なければすぐに処理を終了させている(判定には、
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.size() == 0) {
return true;
}
#参考
- Java技術最前線 - 「Java SE 6完全攻略」第94回 アノテーションを処理する その1:ITpro
- Java技術最前線 - 「Java SE 6完全攻略」第95回 アノテーションを処理する その2:ITpro
- Java技術最前線 - 「Java SE 6完全攻略」第96回 アノテーションを処理する その3:ITpro
- Java技術最前線 - 「Java SE 6完全攻略」第97回 アノテーションを処理する その4:ITpro
- Java技術最前線 - 「Java SE 6完全攻略」第98回 アノテーションを処理する その5:ITpro
- Java技術最前線 - 「Java SE 6完全攻略」第99回 アノテーションを処理する その6:ITpro
- Java技術最前線 - 「Java SE 6完全攻略」第100回 アノテーションを処理する その7:ITpro
- アノーテーションプロセッサーをEclipseで動かすまでに豪華メンバーにサポートを受けるなど - Togetterまとめ
- eclipseでカスタムアノテーションを作るためのメモ: mitsuruogの日記
- javax.annotation.processing (Java Platform SE 8 )
- Java技術最前線 - 「Java SE 6完全攻略」第11回 コンポーネントのロードを行うServiceLoader:ITpro
- vvakame / build.gradle | GitHub Gist
- 第38章 Eclipse プラグイン
- Node (Groovy 2.4.3)
- GradleでEclipseの設定をカスタマイズする - @ikikko のはてなブログ