LoginSignup
9

More than 5 years have passed since last update.

Lombokをカスタマイズして独自のAST変換

Posted at

はじめに

コレは何?

Lombokをカスタマイズして、アノテーションの指定による引数チェックを行うライブラリを作成した際のメモ兼宣伝です。
ソースはGitHubに置いてあります。

動機

  1. メソッドの先頭に毎回、引数チェックの処理を記述する事に疲弊した。
  2. 他のメンバーが作成したAPIのリファレンスに、引数に指定出来る値の範囲が記載されていないくて立腹した。
  3. 他のメンバーが作成したAPIの、リファレンスに書かれている内容と実装とが異なっていて絶望した。
  4. 自分の作成したAPIのリファレンスを見直したら、引数に指定出来る値の範囲が記載されていないくて反省した。
  5. 自分の作成したAPIの、リファレンスに書かれている内容と実装とが異なっていて猛省した。
  6. JSR-305とFindBugsを試してみたが、業務アプリケーションにはあまり適合しない1と思った。
  7. Lombokでnullチェック以外のアサーションも出来れば良いと思った。
  8. なら作ってみよう。

方針

  • 本家側の更新を楽に取り込むために、既存のコードは極力変更しない。
  • デバッグの容易性を重視し2、実行時のライブラリ依存を許容する。
  • 本家が提供するアノテーション(@Setterとか@Dataとか)との組み合わせが困難3なので、最低限の代替手段を提供する。

制限事項

  • 前述の通り、本家が提供するアノテーションとは組み合わせられません(残念の極み)。
  • この記事を書いている時点では、本家のv1.16.4をベースにしているため、Eclipse 4.5 (Mars)で色々と不具合がある模様です。

本家が用意してくれているビルドスクリプトの基本的な使い方

以下の手順は全て、リポジトリのルートディレクトリで行う前提です。
予め本家のリポジトリをそのままcloneするか、Forkしたリポジトリをcloneしておいて下さい。
また、LombokのビルドにはAntが必要になりますので、予めダウンロードしてパスを通しておいて下さい。

Eclipseにインポート

私はEclipseを常用しているので、下記コマンドでEclipseにインポート出来る形式(プロジェクト形式)に変換した後にインポートしました。
なお、プロジェクト名をlombokから変更する場合は、build.xmleclipseターゲットを修正しておく必要があります4

ant eclipse

ビルド

下記コマンドでJARを生成できます。
なお、プロジェクト名をlombokから変更した場合はbuild.xmlbuildScripts/website.ant.xml等を色々と修正する必要があります4

ant dist

テスト

私の環境では一部の設定を修正しないとECJ5を使用したテストがちゃんと動きませんでした。

以下のいずれかのコマンドを実行し、テストを行うJDKのバージョンを指定します。
なお、前述の手順でEclipseのプロジェクト形式に変換してある場合、Eclipseからテストを実行するためのRunLombokTest <JDK名>.launchというファイルが生成されます。

OpenJDK6
ant setupJavaOpenJDK6TestEnvironment
OpenJDK7
ant setupJavaOpenJDK7TestEnvironment
OracleJDK7
ant setupJavaOracle7TestEnvironment
OracleJDK8
ant setupJavaOracle8TestEnvironment

JDKのバージョンを指定した後に、以下のコマンドによりテストを実行します。
なお、JDKのバージョンを切り替えてテストを行う場合は、前述のコマンドにより再度JDKのバージョンを指定した後に以下のコマンドを実行します。

ant test

Eclipseでの動作確認

下記コマンドを実行する事によりLombokizedEclipse.launchというファイルがプロジェクト直下に生成され、メニューのRun As...から、このプロジェクト(でant distで生成したJAR)を適用したEclipseを実行出来るようになります。

ant eclipseForDebugging

LombokizedEclipse.png

その他にやった事

  • 独自の設定項目を追加するためにConfigurationKeys.javaに定義を追記6しました。
  • 何故かtest/core/src/lombok/LombokTestSource.javaだけがLombokを使用しないとコンパイル出来ないコードとなっていたので修正しました(テストコードなのでリソースリークの可能性は気にしない)。
  • 成果物をBintrayにアップロードするためにgradle-bintray-pluginを使用したかったので、かなり強引なbuild.gradleを作成しました。

実装方法

アノテーションを定義する

処理対象の起点となるアノテーションを定義します。

私の場合は、別のライブラリとしてアノテーションとチェックロジックを実装したため、当該のライブラリをbuildScript/ivy.xmldependenciesに追加しました。

なお、私の定義したアノテーションにはRetentionPolicy.RUNTIMEを指定してあります7が、特に理由が無ければRetentionPolicy.SOURCEを指定する方が良さ気です。

ハンドラを実装する

JavacとECJ5とではASTの構造が微妙に異なり、APIは完全に別物です。
そのため、Javac向けのハンドラECJ向けのハンドラをそれぞれ実装する必要があります8

私の場合は、メンテナンスしやすい様に、Javac向けのハンドラとECJ向けのハンドラとで極力同じ構造にしました。
なお、ECJ向けのハンドラでは、Javadocコメントを扱う事が出来ない模様です。

テストする

/test/transform/resourceの下にある各サブディレクトリに、AST変換前のソースと、AST変換後のソースの期待値9やコンパイル時に出力されるメッセージの期待値等を配置します。

before
AST変換前のソース
after-delombok
Javac向けのハンドラによってAST変換された後のソースの期待値
after-ecj
ECJ向けのハンドラによってAST変換された後のソースの期待値
messages-delombok
Javacを使用してコンパイルした際に出力されるメッセージの期待値
messages-ecj
ECJを使用してコンパイルした際に出力されるメッセージの期待値
messages-idempotent
after-delombokにあるソースをコンパイルした際に出力されるメッセージの期待値

私の場合は、既存コードは基本的に修正しない方針(既存のソースはテストされている前提)としたので、自作のハンドラのみをテストするためのテストケースを作成してbuild.xmltestターゲットを修正しました。

ハマった事

アノテーションの単純名

com.develhack.annotation.assertion.NonNullというアノテーションを定義したのですが、対応するハンドラが実行されませんでした。
どうやらLombokはアノテーションの単純名とハンドラの実装とを対応付けて管理している様で、Lombokが元々提供しているlombok.NonNullと単純名が重複していたのが原因でした。
アノテーション名をcom.develhack.annotation.assertion.Nonnull(nが小文字)に変更した所、期待した動作となりました。

ECJでのフィールド追加

EclipseAnnotationHandler#handle()内でASTにフィールドを追加しようとしたら残念な結果になりました。
ECJでフィールドを追加する場合はEclipseAnnotationHandler#handle()ではなくEclipseAnnotationHandler#preHandle()で行う必要がある模様です。

所感


  1. 上限値の指定や下限値の指定、範囲の指定等が出来ない。 

  2. Lombokによって生成されたステートメントはソースコード上に現れないためデバッグが困難。 

  3. 本家の実装が、各ハンドラ内で@Nonnull@Nullableの指定有無をそれぞれ(正規表現によるcase-insensitiveなマッチングで)判定しているため、それ以外のアノテーションに対応しようとすると修正箇所が非常に多くなってしまう。 

  4. 修正した箇所の全量は初回コミットの差分参照(追加したハンドラも含まれているけど・・・)。 

  5. Eclipse Compiler for Java 

  6. 最初は別のクラスとして定義しようとしたが、そっちのほうが修正箇所が多くなってしまいそうだった。 

  7. Lombokと組み合わせなくてもリフレクション等で使用出来るようにしておきたかった。 

  8. ぶっちゃけ、Javac向けのハンドラとECJ向けのハンドラの両者を実装するのは苦行です・・・。 

  9. AST変換はソースコードを変換する訳ではないので、正確には「変換後のASTをソースコードで表現した場合はこうなっているハズ」の期待値。 

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
9