Compiler Tree API で Java ソースファイルをパースする
javax.tools と Compiler Tree API を使って Java のソースファイルをパースしてみました。
- Java SE 8u31
前回 と同じように、ソースファイルを AST 化した CompilationUnitTree (実際は JCTree$JCCompilationUnit) を取得し、簡単な TreeVisitor を適用します。
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20150208/
はじめに
ソースファイルを CompilationUnitTree 化する一般的な方法が分からなかったので、とりあえず以下のような方法で試してみました。 (com.sun.tools.javac.main.JavaCompiler は Compiler Tree API ではありませんが)
- (a) com.sun.tools.javac.main.JavaCompiler を利用
- (b) com.sun.source.util.JavacTask を利用
今回は、取得した CompilationUnitTree へ下記の TreeVisitor 実装クラスを適用します。
SampleVisitor.java
import com.sun.source.util.TreeScanner; import com.sun.source.tree.*; public class SampleVisitor extends TreeScanner<Void, Void> { @Override public Void visitClass(ClassTree node, Void p) { // クラス名の出力 System.out.println("class: " + node.getSimpleName()); return super.visitClass(node, p); } @Override public Void visitMethod(MethodTree node, Void p) { // メソッド名の出力 System.out.println("method: " + node.getName()); return super.visitMethod(node, p); } @Override public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { // ラムダ式の内容を出力 System.out.println("lambda: " + node); return super.visitLambdaExpression(node, p); } }
SampleVisitor は以下の内容を出力します。
(a) com.sun.tools.javac.main.JavaCompiler 利用
com.sun.tools.javac.main.JavaCompiler
を使った処理内容は以下の通りです。
- (1) javax.tools.ToolProvider から javax.tools.JavaCompiler 取得
- (2) javax.tools.JavaCompiler から StandardJavaFileManager 取得
- (3) StandardJavaFileManager から JavaFileObject (の Iterable) 取得
- (4) JavaFileObject を com.sun.tools.javac.main.JavaCompiler で parse して CompilationUnitTree (JCTree$JCCompilationUnit) を取得
- (5) CompilationUnitTree へ TreeVisitor を適用
javax.tools.JavaCompiler
と com.sun.tools.javac.main.JavaCompiler
は別物なのでご注意ください。
StandardJavaFileManager
の getJavaFileObjects
メソッドはソースファイル名を可変長引数の文字列で与えれば JavaFileObject (の Iterable) を取得できるので、コマンドライン引数の args をそのまま渡しています。
JavaCompilerParse.java
import java.io.IOException; import javax.tools.ToolProvider; import javax.tools.StandardJavaFileManager; import com.sun.source.tree.CompilationUnitTree; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.util.Context; public class JavaCompilerParse { public static void main(String... args) throws IOException { try ( // (1) (2) StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler() .getStandardFileManager(null, null, null) ) { JavaCompiler compiler = new JavaCompiler(new Context()); // (3) fileManager.getJavaFileObjects(args).forEach(f -> { // (4) AST 取得 CompilationUnitTree cu = compiler.parse(f); // (5) クラス・メソッド名とラムダ式の出力 cu.accept(new SampleVisitor(), null); }); } } }
JDK の lib/tools.jar を CLASSPATH へ指定してコンパイルする必要があります。
コンパイル
> javac -cp %JAVA_HOME%/lib/tools.jar *.java
実行する際も lib/tools.jar が必要です。
今回は適当に用意した Sample.java をパースしてみました。
実行結果
> java -cp .;%JAVA_HOME%/lib/tools.jar JavaCompilerParse Sample.java class: Sample method: sample1 method: sample2 lambda: (n)->n * 2 class: SampleChild method: child1 lambda: (n)->{ System.out.println(n); return n > 0 && n % 2 == 0; } method: child2
使用した Sample.java の内容は以下の通りです。
Sample.java
import java.util.stream.IntStream; class Sample { public String sample1() { return "sample"; } public int sample2(int x) { return IntStream.range(0, x).map(n -> n * 2).sum(); } class SampleChild { public void child1(int... nums) { IntStream.of(nums).filter(n -> { System.out.println(n); return n > 0 && n % 2 == 0; }).forEach(System.out::println); } private void child2() { } } }
(b) com.sun.source.util.JavacTask 利用
com.sun.source.util.JavacTask
には、CompilationUnitTree (の Iterable) を取得する parse
メソッドが用意されていますが、JavacTask を取得する一般的な方法が分からなかったので、今回は javax.tools.JavaCompiler
の getTask
メソッドで取得した JavaCompiler.CompilationTask
の実装オブジェクト (com.sun.tools.javac.api.JavacTaskImpl
) を JavacTask
へキャストしました。
- (1) javax.tools.ToolProvider から javax.tools.JavaCompiler 取得
- (2) javax.tools.JavaCompiler から StandardJavaFileManager 取得
- (3) javax.tools.JavaCompiler を使って StandardJavaFileManager と JavaFileObject (の Iterable) から JavacTask 取得
- (4) JavacTask を parse して CompilationUnitTree (の Iterable) を取得
- (5) CompilationUnitTree へ TreeVisitor を適用
JavacTaskParse.java
import java.io.IOException; import javax.tools.ToolProvider; import javax.tools.JavaCompiler; import javax.tools.StandardJavaFileManager; import com.sun.source.util.JavacTask; public class JavacTaskParse { public static void main(String... args) throws IOException { // (1) JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); try ( // (2) StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null) ) { // (3) JavacTask task = (JavacTask)compiler.getTask(null, fileManager, null, null, null, fileManager.getJavaFileObjects(args)); // (4) (5) task.parse().forEach(cu -> cu.accept(new SampleVisitor(), null)); } } }
実行結果
> java -cp .;%JAVA_HOME%/lib/tools.jar JavacTaskParse Sample.java class: Sample method: sample1 method: sample2 lambda: (n)->n * 2 class: SampleChild method: child1 lambda: (n)->{ System.out.println(n); return n > 0 && n % 2 == 0; } method: child2