前回まで,Compiler APIの基本的な使用法を解説してきました。今月はCompiler APIの応用編です。
今週はプログラム中で動的にソースを生成して,コンパイルする手法を紹介します。
前回,Javaのソースをコンパイルする機能を持つjavax.tools.JavaCompilerインタフェースは,仮想的なファイルマネージャであるjavax.tools.StandardJavaFileManagerインタフェースを使用することを紹介しました。そして,JavaFileManagerインタフェースが操作するのがjavax.tools.JavaFileObjectインタフェースです。
このJavaFileObjectインタフェースがファイルに対応しています。
動的にソースを生成する場合,そのソースは文字列として表されます。しかし,JavaFileObjectインタフェースはファイルに対応しているため,文字列で表されたJavaのソースに対応することはできません。
そこで,JavaFileObjectインタフェースを拡張して,文字列でも扱えるようにします。
とはいっても,JavaFileObjectインタフェースを直接拡張するわけではなく,JavaFileObjectインタフェースを実装しているjavax.tools.SimpleJavaFileObjectクラスを派生させたクラスを作成します。
実際にコンパイルを行うJavaCompiler.CompilationTaskオブジェクトはJavaFileObjectオブジェクトのgetCharContentメソッドをコールして,Javaのソースを取得します。デフォルトで使用されるJavaFileObjectオブジェクトはここでファイルから読み込みを行います。
そこで,getCharContentメソッドの戻り値に自身が保持している文字列を返すようにします。
以下にStringJavaFileObjectクラスを示します。
public class StringJavaFileObject extends SimpleJavaFileObject {
private String content;
// 文字列をコンテンツとする JavaFileObject
public StringJavaFileObject(String className, String content) {
// スキーマは string
// 第1引数はクラス名なので,最後に .java を付加する
super(URI.create("string:///"
+ className.replace('.','/')
+ Kind.SOURCE.extension),
Kind.SOURCE);
// コンストラクタの第2引数をJavaのソースとする
this.content = content;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}
StringJavaFileObjectクラスのコンストラクタでは,まずスーパークラスのSimpleJavaFileObjectクラスのコンストラクタをコールします(青字部分)。
SimpleJavaFileObjectクラスのコンストラクタは第1引数がJavaのソースファイルに対応するjava.net.URIオブジェクト,第2引数がファイルの種別を表すenumのJavaFileObject.Kindです。
ここでは,文字列に対応するURIのスキーマをstringとしています。また,パッケージの. (ピリオド)はすべて/ (スラッシュ)に変更します。また,StringJavaFileObjectクラスのコンストラクタの第1引数はクラス名なので,最後に拡張子の.javaを加えています。
JavaFileObject.Kindにはextensionというフィールドを持っており,列挙する要素に応じた拡張子を保持しています。ここでは,Javaのソースに対応するJavaFileObject.Kind.SOURCEを使用しました。
第2引数もソースを表すJavaFileObject.Kind.SOURCEを使用します。
StringJavaFileObjectクラスのコンストラクタ第2引数はそのままJavaのソースとしました(オレンジ字部分)。したがって,赤字で示したようにgetCharContentメソッドでは,そのままcontentを返しています。