昔書いた記事で、TreePathScannerを使ってVisitorパターンを使ってクラスの情報をとってきてました。
http://blogs.wankuma.com/kazuki/archive/2007/12/20/113814.aspx
これ以外にも、やり方があることがわかったのでメモしておきます。2年越しの関連記事になります。
とりあえずTreeApiSampleという名前でNetBeansのモジュールプロジェクトを作成します。
ここに、クラスの情報をとっとくためのクラスをClazzという名前で作成します。
package okazuki.treeapi; import java.util.ArrayList; import java.util.List; public class Clazz { // クラス名 private String name; // メソッド名のリスト private List<String> methods = new ArrayList<String>(); public List<String> getMethods() { return methods; } public void setMethods(List<String> methods) { this.methods = methods; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
上のクラスに、データを詰め込む仕事をするクラスを作成します。今回の目玉!まず、依存するライブラリをプロジェクトに追加します。追加するライブラリは、Java ソースとJavac API ラッパー, ファイルシステム API, データシステム APIの4つです。
そして、Clazzを作成するクラスなのでClazzMakerという名前のクラスを作成してFileObjectを受け取るmakeメソッドを作成します。とりあえず、どんがらだけ作成すると、以下のような感じです。このmakeメソッドを実装していきます。
package okazuki.treeapi; import org.openide.filesystems.FileObject; public class ClazzMaker { /** * 引数で渡されたFileObject内にある最初のクラス定義の情報を返す */ public Clazz make(FileObject javaFileObject) { // TODO : 実装する return null; } }
さくっと実行していきます。ポイントはCompilationUnitを使ってTreeを取得して、そいつに対して色々やってる点です。
package okazuki.treeapi; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.Task; import org.openide.filesystems.FileObject; public class ClazzMaker { /** * 引数で渡されたFileObject内にある最初のクラス定義の情報を返す */ public Clazz make(FileObject javaFileObject) { try { // FileObjectからJavaSourceを作成して JavaSource source = JavaSource.forFileObject(javaFileObject); if (source == null) return null; // タスクを実行 MakeClazzTask task = new MakeClazzTask(); source.runUserActionTask(task, true); // タスクの実行結果を返す return task.getCreatedClazz(); } catch (IOException ex) { Logger.getLogger(ClazzMaker.class.getName()).log(Level.SEVERE, null, ex); // 駄目だったのでおしまい return null; } } /** * Clazzクラスの作成を実際にやるタスク */ private static class MakeClazzTask implements Task<CompilationController> { // このタスクで作成されたクラスの情報 private Clazz createdClazz; public void run(CompilationController parameter) throws Exception { // CompilationUnitを取得して CompilationUnitTree unit = parameter.getCompilationUnit(); // 定義されている型を取得して List<? extends Tree> types = unit.getTypeDecls(); // 何か定義されていて、クラスだったら if (types.isEmpty()) return; if (types.get(0).getKind() != Tree.Kind.CLASS) return; // 安心してClassTreeを取り出す ClassTree type = (ClassTree) types.get(0); // Clazzに名前を設定する Clazz clazz = new Clazz(); // メソッドの情報を収集する clazz.setName(type.getSimpleName().toString()); for (Tree member : type.getMembers()) { // メソッドじゃなかったら何もしない if (member.getKind() != Tree.Kind.METHOD) continue; // メソッドなので安心してキャスト MethodTree method = (MethodTree) member; clazz.getMethods().add(method.getName().toString()); } // 最後までうまくいったのでフィールドに格納 createdClazz = clazz; } public Clazz getCreatedClazz() { return createdClazz; } } }
次に、こいつを使うアクションを作成します。TraceActionという名前で、TraceClassというラベルで作成しました。この中で、IOのAPIを使うのでライブラリに入出力 APIを追加しておきます。ClazzMakerを使うだけなので、さくっとやっちゃいます。
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package okazuki.treeapi; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import org.openide.filesystems.FileObject; import org.openide.loaders.DataObject; import org.openide.windows.IOProvider; import org.openide.windows.InputOutput; public final class TraceAction implements ActionListener { private final DataObject context; public TraceAction(DataObject context) { this.context = context; } public void actionPerformed(ActionEvent ev) { FileObject obj = context.getLookup().lookup(FileObject.class); InputOutput io = IOProvider.getDefault().getIO("Trace", false); if (obj == null) { io.getOut().println("FileObject not found"); return; } ClazzMaker maker = new ClazzMaker(); Clazz clazz = maker.make(obj); if (clazz == null) { io.getOut().println("クラス情報の取得に失敗しました"); return; } io.getOut().printf("%s : %s\n", clazz.getName(), clazz.getMethods()); } }
このモジュールを実行した先のNetBeansで簡単に以下のようなクラスを作成します。
package sampleapp; public class Main { public static void main(String[] args) {} public void foo(){} }
このクラスを選択した状態で、先ほど作ったメニューを選択すると、出力ウィンドウに以下のような結果が表示されます。
Main : [<init>, main, foo]
うん、ばっちりクラス名とメソッド名が取れてます。素敵。
クラス名を短い名前じゃなくてFQCNで取りたいときは、以下のようにしてClassTreeからTypeElementに変換できます。
CompilationUnitTree unit = parameter.getCompilationUnit(); Element elm = parameter.getTrees().getElement(parameter.getTrees().getPath(unit, type)); if (elm == null) return; TypeElement typeElement = (TypeElement) elm; // Clazzに名前を設定する Clazz clazz = new Clazz(); clazz.setName(typeElement.getQualifiedName().toString());
以上