結構前にも同じようなことを題材に書いたような気がするけど、人間とは忘れる生き物です。
もれなく俺も人間なので、きれいさっぱり忘れてしまった。
ということで復習してみよう!
ちなみに、やろうとしてることは、NetBeansのプラグインを作って、そこからプロジェクト内の選択されたJavaのクラスのフィールドやらメソッドやらをとってしまおうという感じのものです。
とりあえず、プロジェクトを作成して、必要そうなライブラリを追加しておく。
因みに、参考にしてるのはこっちのページです。
さくっとToolbarあたりにボタンを1つ追加する。
アイコンも参考にしてるサイトから虹っぽいやつを拝借させてもらった。
これでクリックしたら何かできるとっかかりができた。ここにいろいろ書いていく。
とりあえず、動作確認のために簡単なのを書いてみた。
protected void performAction(Node[] activatedNodes) {
DataObject dataObject = activatedNodes[0].getLookup().lookup(DataObject.class);
StatusDisplayer.getDefault().setStatusText("こんにちは世界!!");
}
この状態で実行すると、NetBeansのToolbarに新しいボタンが追加される。
何かファイルとかを選択すると、押せるようになるので、押してみるとステータスバーにこんにちは世界!!と表示される。
よし、とりあえずの動きはよさそうだ。
とりあえず、選択されたファイルから、Javaのクラスの情報とるための下準備のコードを書いていく。
まず、DataObjectからFileObjectを取得して、そこからJavaSourceを取得するところまで書いてみる。
JavaSourceが取得できれば、そこから色々なことができるようになって夢広がるっぽい。
早速、さっきの「こんにちは世界!!」を消して下のコードを書いてみたよ!
protected void performAction(Node[] activatedNodes) {
DataObject dataObject = activatedNodes[0].getLookup().lookup(DataObject.class);
FileObject selectedFile = dataObject.getPrimaryFile();
JavaSource selectedJavaSource = JavaSource.forFileObject(selectedFile);
if (selectedJavaSource != null) {
StatusDisplayer.getDefault().setStatusText(selectedJavaSource + "が取得できた");
} else {
StatusDisplayer.getDefault().setStatusText("Javaのクラスじゃない…");
}
}
実行するとこんな感じ。
左は、パッケージを選択した状態でボタンを押したときのメッセージ。右は、Main.javaを選択した状態でボタンを押したときのメッセージ。
ここまでは、順調にできてる!!
さて、ちょいと話が変わるけど、今回ツールバーに追加したボタンは、Javaのファイルを選択したときにしか押してもらいたくない。
んで、さっきのコードの動きを見る限り、Javaのファイル以外のときは、JavaSourceの取得のところでnullになる。
ってことで、enableメソッドをオーバーライドしてちょろっとコードを書く。
/**
* 有効・無効の判断をする。JavaSourceが取得できるものは有効です。
*/
@Override
protected boolean enable(Node[] arg) {
if (super.enable(arg)) {
return getSelectedJavaSource(lookupDataObject(arg)) != null;
}
return false;
}
/**
* DataObjectからJavaSourceを取得する。
*/
private JavaSource getSelectedJavaSource(DataObject dataObject) {
FileObject selectedFile = dataObject.getPrimaryFile();
return JavaSource.forFileObject(selectedFile);
}
/**
* 選択されてるノードから、DataObjectを取得する。
*/
private DataObject lookupDataObject(Node[] activatedNodes) {
return activatedNodes[0].getLookup().lookup(DataObject.class);
}
これで、Javaのファイルを選択したときだけツールバーのボタンが有効になる。
段々、目的の処理を書くところに近づいてる。
JavaSourceについて、何か処理をするときは、runUserActionメソッドを呼び出してやるっぽい。
そこには、Task<CompilationController>を渡して、そのrunメソッドで処理を書く。
というわけで処理の中身を書く一歩手前まで書いてみる。
protected void performAction(Node[] activatedNodes) {
DataObject dataObject = lookupDataObject(activatedNodes);
JavaSource selectedJavaSource = getSelectedJavaSource(dataObject);
try {
selectedJavaSource.runUserActionTask(new Task<CompilationController>() {
public void run(CompilationController arg0) throws Exception {
// ここに色々書いていく
}
}, true);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
これで準備は整ったはず!ガンガン書いていく!
まず、runメソッドの引数のCompilationControllerをELEMENT_RESOLVEまで持っていってクラス名とかくらいがとれるようにしておく。
クラス名、メソッド名の一覧の取得のためのコードだけど…
結構長いことになった。とりあえずコードを張っておく。
protected void performAction(Node[] activatedNodes) {
DataObject dataObject = lookupDataObject(activatedNodes);
JavaSource selectedJavaSource = getSelectedJavaSource(dataObject);
try {
final StringBuilder sb = new StringBuilder();
selectedJavaSource.runUserActionTask(new Task<CompilationController>() {
public void run(final CompilationController compilationController) throws Exception {
// 要素が見つかるくらいまでは処理してねと
compilationController.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
new TreePathScanner<Void, Void>(){
@Override
public Void visitClass(ClassTree tree, Void arg) {
TreePath path = getCurrentPath();
Element element = compilationController.getTrees().getElement(path);
TypeElement type = (TypeElement) element;
sb.append(type.getQualifiedName()).append("\n");
List<? extends Element> enclosedElements = type.getEnclosedElements();
for (Element elm : enclosedElements) {
sb.append("\t").append(elm.getKind()).append(": ").append(elm.getSimpleName()).append("\n");
}
return null;
}
}.scan(compilationController.getCompilationUnit(), null);
InputOutput io = IOProvider.getDefault().getIO("Dump", true);
io.getOut().println(sb);
io.getOut().close();
}
}, true);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
肝になるのは、TreePathScannerのvisitClassをオーバーライドしてるあたり。
このメソッド内にいるってことは、現在解析がクラスの定義まで進んでるってことだから、getCurrentPathで現在位置をとってきてgetElementでクラス定義を表すTypeElementがとってこれる(と信じてる)。
あとは、getEnclosedElementsでElementの中身をStringBuilderに追加していってる。
最後に、Outputウィンドウに結果を出力してる。
適当にフィールドやメソッドを作ったクラスにたいして、このコードを走らせるとOutputウィンドウに下のような感じで表示された。
javaapplication1.Main
CONSTRUCTOR: <init>
FIELD: value
METHOD: getValue
METHOD: setValue
METHOD: main
ちゃんととれてるっぽい。
後は、フィールドの型や、メソッドの引数や戻り値がとれれば大体大丈夫かな?
今日は、もう眠いのでここらへんでダウン。
ってか、このエントリーかくのに、のべ4時間くらい使ってそうな予感…
TreePathScannerを使わないで、同じ情報取得する方法がないかな~って探し回ってたのが敗因だった。
だって、TreePathScannerをいちいち継承するのがめんどくさい!でも、今日調べた範囲だと、ほかにいい方法がみつからなかった。
誰か、素敵な方法を知ってたら教えてくれないかなぁ