BLOGTIMES
2010/05/22

Eclipse の ASTParser を単体で使う

  java  eclipse 
このエントリーをはてなブックマークに追加

ASTParserを単体で使う - Eclipse の ASTParser を単体で使う

ちょっと Java プログラムの解析をしないといけないことになったので、 JavaCC をしばらくいじっていたのですが、どうもしっくり来ないのでなにか他に良い選択肢はないかなとしばらく考えていました。

以前に後輩がEclipseプラグインを作ったときには、けっこう良い具合にASTが出来ていたことを思い出したので、もしかしてEclipseのParserが単体で使えたりしないかなとちょっと調べてみたところ、 IBM のサイトで「EclipseのASTParserを試す」という記事をみつけました。残念ながらこの記事の内容はちょっと古く、 JLS2 (Java1.4) のプログラムしか解析できません。今回は JLS3 (Java5) 対応が必須だったのでこれは十分ではありません。

仕方がないので日本語のリソースを諦めて、 ASTParser の FQCN である org.eclipse.jdt.core.dom.ASTParser をキーワードにいろいろと検索して見ると、 JDT のメーリングリストの下記の議論で ASTParser を単独で動かすことができるという記述を見つけました。

[news.eclipse.tools.jdt] Re: Using ASTParser with binding resolving in a standalone application

Exactly. Simple AST parsing without binding resolving of Java source files as char[] works perfectly. These jars are needed in order to make this work outside the Eclipse context:
- org.eclipse.core.contenttype_3.2.100.v20070319.jar
- org.eclipse.core.jobs_3.3.1.R33x_v20070709.jar
- org.eclipse.core.resources_3.3.1.R33x_v20080205.jar
- org.eclipse.core.runtime_3.3.100.v20070530.jar
- org.eclipse.equinox.common_3.3.0.v20070426.jar
- org.eclipse.equinox.preferences_3.2.101.R33x_v20080117.jar
- org.eclipse.jdt.core_3.3.3.v_793_R33x.jar
- org.eclipse.osgi_3.3.2.R33x_v20080105.jar

結論から述べるとこの Tips は現在も有効で、自分の PC の Galileo から上記と同じ Jar (バージョンは新しくなっていますが)を取り出すことで ASTParser 単体での動作が可能でした。実際にビルドして、サンプルのプログラムからメソッドのシグニチャを取り出しているのが上記の画像になります。

抽象構文木はコンパイラの授業で習ったし、デザインパターンもきちんと理解しているつもりなので、理解するための素地は整っているとはいえ、普段は処理系とかあまり低レベルな処理に手を出していない人なのでこの作業は正直なかなかしんどかったです。珍しく夜を明かしてしまいましたが、まぁ実質6時間くらいでできたので、それでも省エネだったとは思いますが。

以下、作業メモを残しておきます。

ASTParser を使うための参考資料

ASTParser の API の使い方については前述のIBMの記事の他、「JDT プラグイン・デベロッパー・ガイド - org.eclipse.jdt.core.dom」が非常に役立ちます。そのほかにデザインパターンの Visitor パターンをきちんと理解しているというのは大前提になりますが。

また、実際に Eclipse が AST をどのように生成しているかについては、「org.eclipse.jdt.astview - AST View」というプラグインを Eclipse にインストールすることでリアルタイムに表示させることが出来ます。開発中には上記のAPIとこのプラグインがとても参考になりました。

また、これも API の注意事項に書かれていることですがハマったのでメモしておきます。 AST にはコメントのノードは入っていません。CompilationUnit#getCommentList()を使って別途取り出す必要があります。

作成したサンプル

ビルドパスに追加したライブラリ*1

commons-lang-2.5.jar
org.eclipse.core.contenttype_3.4.1.R35x_v20090826-0451.jar
org.eclipse.core.jobs_3.4.100.v20090429-1800.jar
org.eclipse.core.resources_3.5.2.R35x_v20091203-1235.jar
org.eclipse.core.runtime_3.5.0.v20090525.jar
org.eclipse.equinox.common_3.5.1.R35x_v20090807-1100.jar
org.eclipse.equinox.preferences_3.2.301.R35x_v20091117.jar
org.eclipse.jdt.core_3.5.2.v_981_R35x.jar
org.eclipse.osgi_3.5.2.R35x_v20100126.jar

ASTTest.java(メインクラス)

import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; public class ASTTest { public static void main(String[] args) { if (args.length != 1) { System.out.println("File is not defined."); System.exit(1); } try { System.out.println("Reading " + args[0] + " . . ."); StringBuffer sb = new StringBuffer(); BufferedReader br = new BufferedReader(new InputStreamReader( new FileInputStream(args[0]))); String line; while( (line = br.readLine()) != null ){ sb.append(line+"\n"); } // Create AST Parser ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setSource(sb.toString().toCharArray()); CompilationUnit unit = (CompilationUnit) parser.createAST(new NullProgressMonitor()); // Visit Node MyVisitor visitor = new MyVisitor(); unit.accept(visitor); System.out.println("Done !"); } catch ( FileNotFoundException e) { System.out.println("File " + args[0] + " not found."); return; } catch ( IOException e ){ System.out.println("IO Exception !"); return; } } }

MyVisitor.java(Visitorクラス)

import org.apache.commons.lang.StringUtils; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.MethodDeclaration; public class MyVisitor extends ASTVisitor { @Override public boolean visit(MethodDeclaration node) { StringBuffer sb = new StringBuffer(); //Modifiers sb.append(StringUtils.join(node.modifiers(), " ")); sb.append(" "); // Return Type if( !node.isConstructor() ){ sb.append(node.getReturnType2().toString() ); sb.append(" "); } // Parameters sb.append(node.getName().toString()); sb.append("("); sb.append(StringUtils.join(node.parameters(), ", ")); sb.append(")"); System.out.println(sb); return super.visit(node); } }

Test.java(パース対象としたJavaファイル。先日のjjmltの時のものをちょっと改造)

import java.util.ArrayList; import java.util.List; public class Foo { enum TYPE {AAA, BBB, CCC}; public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("Foo"); list.add("Bar"); list.add("Baz"); for (String str : list) { System.out.println(str + " "); } for (TYPE type : TYPE.values()) { System.out.println(type.name() + " "); } } public void method1(String[] arg0, int arg 1) { list.add(""); } public void method2(int arg0, int arg2, int arg3) { list.add(""); } }
  • *1: commons-langはASTParserの動作には必須ではない。

トラックバックについて
Trackback URL:
お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3567
Trackbacks
このエントリにトラックバックはありません
Comments
愛のあるツッコミをお気軽にどうぞ。[policy]
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
コメントはありません
Comments Form

コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。

OpenID を使ってログインすることができます。

Identity URL: Yahoo! JAPAN IDでログイン