LoginSignup
0
0

More than 1 year has passed since last update.

AnnotationProcessorでプログラムの自動生成をするときに役に立つかもしれない記事

Posted at

AnnotationProcessorでプログラムの自動生成

本記事はあまりにも情報が少ないため書いた記事です。本なら情報があると思うのですが、ネットでは見つけられませんでした。
そして、書いてる人は使うことをあきらめました。

プログラムを一から生成する場合こちらで紹介する方法ではなく、テンプレートなどからJavaFileObjectを生成したほうが楽です。

代替手段

  • リフレクションで実行時に何とかする - 動作速度は遅いですが情報が多いです。
  • 部分的にJavacParserで作成して差し替え - 圧倒的に楽になると思います。

環境

  • JDK corretto-11
  • Gradle gradle 7.4
  • Intellij IDEA 2022.1

gradleの設定

AnnotationProcessorを作成するプロジェクトにいかを追加します。

dependencies {
    annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
    compileOnly 'com.google.auto.service:auto-service:1.0.1'

    if (targetCompatibility.toString().startsWith("1."))
        implementation files("${System.properties['java.home']}/../lib/tools.jar")
}

compileJava {
    if (!targetCompatibility.startsWith("1.")) {


        doFirst {
            options.compilerArgs = ["--module-path", classpath.asPath,
                                    "--add-modules", "com.google.auto.service",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
                                    "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED"]
            classpath = files()
        }
    }
}

具体的に何をしているかというと、使用するクラスがJDKのバージョンが8以下の場合tools.jarに、それ以外の場合モジュールシステムで隠蔽されているので強制的にexportしています。
また、本来なら自分でMETA-INF/service/javax.annotation.processing.ProcessorにAnnotationProcessorの情報を記述しないといけないのですが、googleの auto.service@AutoServiceアノテーションをつけると自動で記述してくれます。

クラス,メソッドを取得する。

アノテーションがつけられているクラスやメソッドを取得します。

package com.example
import com.google.auto.service.AutoService;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.model.*;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;

@AutoService("Processor.class")
@SupportedAnnotationTypes("*")
public class AnnotationProcessor extends AbstractProcessor{

  JavacElements elementUtils;
  Context context;
  TreeMaker maker;
  Names names;

  @Override
  pulbic synchronized void init(ProcessingEnvironment processingEnv){
  elementUtils = ((JavacElements)processingEnv.getElementUtils());
  context = ((JavacElements)processingEnv.getContext());
  maker = TreeMaker.instance(context);
  names = Names.instance(context);
}

  @Override
  public boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv){
    for (Element element : roundEnv.getElementsAnnotatedWith(Override.class){
      JCTree tree elementUtils.getTree(element);
      JCMethodDecl jcMethodDecl = (JCMethodDecl)tree;
      //             ↑これ
    }
  }
}

なお以下からprocessの中身または一部のみだけ書きます。

一つ上のTreeを取得する。

メソッドならメソッドが所属するクラスを取得します。

for (Element element : roundEnv.getElementsAnnotatedWith(Override.class){
  Element enclosingElement = element.getEnclosingElement();
  //           ↑これ
}

クラスにメソッドを作成する。

classDecl.defs = classDecl.defs.prepend(maker.MethodDef(maker.Modifiers(Flags.PUBLIC),
names.fromString("methodName"), //メソッド名
Maker.Ident(names.fromString("String")), //返り値の型
List.nil(),//ジェネリクスの<T,K>←これ
List.nil(),// パラメータ(引数)
List.nil(),// throwされる例外
null, //Block(後述)
null //初期化(用途不明)
));

メソッドを実行する


maker.exec(
  maker.Apply(
    null, //ジェネリクス
    maker.Select( //実行するメソッドのインスタンス?
      maker.Ident(
        names.fromString("Integer") //インスタンス等を名前で指定
      ),
      names.fromString("parseInt") //メソッド名を指定
    ),
    args //メソッドに渡す引数 例えば List.of(maker.Literal("100"))
  )
);

変数の作成

maker.VarDef(
  maker.Modifiers(Flags.PUBLIC), // PublicやParameterなど
  names.fromString("varName"), // 変数名
  maker.Ident(names.fromString("int")), //型
  exec.getExpression() //初期化 この場合↑の結果を代入している
)

その他

  • いろいろな変数がPublicなのをいいことに直接編集するとエラーが多発するので普通に作成したほうがいいです。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0