WARNING: この記事の内容は、2014/02に記述したメモです。内容が古くなっていますので、ご注意ください。
あと、間違っている記述があるかもしれません。。。
概要
Eclipseの内部で使われているJavaのASTパーサー (org.eclipse.jdt.core.dom.ASTParser
) を使って、Javaのソースコードを解析してみた話。大学時代に弄ってたもの。
正直なところ、この記事の内容はこちらのページにのほうが詳しい。
何ができたか
つくったもの: headless
MySQLのデータベースに、こんなテーブル達を作成して、ASTから取得できる情報をぶち込む代物です。
mysql> show tables;
+--------------------+
| Tables_in_headless |
+--------------------+
| element |
| element_location |
| method |
| method_call_map |
| package |
| type |
| type_hierarchie |
+--------------------+
7 rows in set (0.02 sec)
こんなことして遊んだりできます。
(以下は、 hanzawa_java を処理してみた例です。)
mysql> select type_text, element_text from element where node_kind_text="Variable" limit 3\G
*************************** 1. row ***************************
type_text: ExprEditor
element_text: editor
*************************** 2. row ***************************
type_text: String
element_text: agentArgs
*************************** 3. row ***************************
type_text: Instrumentation
element_text: inst
3 rows in set (0.00 sec)
ファイル内での各言語要素の出現位置も記録します。
mysql> select * from element_location limit 3\G
*************************** 1. row ***************************
map_id: 1
pakage_id: 3
type_id: 1
method_id: NULL
element_id: 1
file: file:/Users/hiraro/Dropbox/workspace/headless_workspace/hanzawa_java/src/net/umanohone/Hanzawa.java
offset: 417
length: 7
line_start: 15
line_end: 15
last_update: 2016-12-04 21:00:03
*************************** 2. row ***************************
map_id: 2
pakage_id: 3
type_id: 1
method_id: NULL
element_id: 2
file: file:/Users/hiraro/Dropbox/workspace/headless_workspace/hanzawa_java/src/net/umanohone/Hanzawa.java
offset: 436
length: 20
line_start: 15
line_end: 15
last_update: 2016-12-04 21:00:03
*************************** 3. row ***************************
map_id: 3
pakage_id: 3
type_id: 1
method_id: NULL
element_id: 3
file: file:/Users/hiraro/Dropbox/workspace/headless_workspace/hanzawa_java/src/net/umanohone/Hanzawa.java
offset: 828
length: 10
line_start: 28
line_end: 28
last_update: 2016-12-04 21:00:03
3 rows in set (0.01 sec)
メソッド呼び出し関係など。
mysql> select method_id, name, signature, length from method order by length DESC limit 3\G
*************************** 1. row ***************************
method_id: 3
name: transform
signature: protected byte[] transform(byte[])
length: 337
*************************** 2. row ***************************
method_id: 2
name: transform
signature: public byte[] transform(java.lang.ClassLoader, java.lang.String, Class<?>, java.security.ProtectionDomain, byte[]) throws java.lang.instrument.IllegalClassFormatException
length: 244
*************************** 3. row ***************************
method_id: 1
name: premain
signature: public static void premain(java.lang.String, java.lang.instrument.Instrumentation)
length: 109
3 rows in set (0.00 sec)
mysql> select * from method_call_map limit 3\G
*************************** 1. row ***************************
map_id: 1
text: inst.addTransformer(new Hanzawa())
caller_id: 1
callee_id: 4
last_update: 2016-12-04 21:00:03
*************************** 2. row ***************************
map_id: 2
text: transform(classfileBuffer)
caller_id: 2
callee_id: 3
last_update: 2016-12-04 21:00:04
*************************** 3. row ***************************
map_id: 3
text: ClassPool.getDefault()
caller_id: 3
callee_id: 5
last_update: 2016-12-04 21:00:05
3 rows in set (0.00 sec)
実装について
必要なライブラリなど
org.eclipse.core.contenttype_*.jar
org.eclipse.core.jobs_*.jar
org.eclipse.core.resources_*.jar
org.eclipse.core.runtime_*.jar
org.eclipse.equinox.common_*.jar
org.eclipse.equinox_preferences_*.jar
org.eclipse.jdt.core_*.jar
org.eclipse.osgi_*.jar
Mavenで依存性解決したらこんな感じだった。
<dependencies>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>contenttype</artifactId>
<version>3.4.200-v20130326-1255</version>
</dependency>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>jobs</artifactId>
<version>3.5.300-v20130429-1813</version>
</dependency>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>org.eclipse.core.resources</artifactId>
<version>3.7.100</version>
</dependency>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>org.eclipse.core.runtime</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.app</artifactId>
<version>1.3.100</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>common</artifactId>
<version>3.6.200-v20130402-1505</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>preferences</artifactId>
<version>3.5.100-v20130422-1538</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>preferences</artifactId>
<version>3.5.100-v20130422-1538</version>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.7.1</version>
</dependency>
</dependencies>
ASTの構築・解析(ソースコードを文字列でぶっこむ方式)
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setSource(srcStr.toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
CompilationUnit unit = (CompilationUnit) parser.createAST(new NullProgressMonitor());
unit.accept(new ASTVisitor(){
@Override
public boolean visit(MethodDeclaration node) {
/* do something about MethodDeclaration node */
return super.visit(node);
}
});
ASTParser
のインスタンスを作って、解析したいソースコード文字列をchar
配列としてぶっこむ。
ちなみに、parser.setKind
のところの引数には、K_COMPILATION_UNIT
の他に、K_EXPRESSION
とかK_STATEMENTS
なども渡すことができる(詳細不明)。
構築したASTは、Visitorパターン
で順次訪問していける。ASTVisitor
クラスを継承して、独自のvisit
メソッドなどをオーバーライドすればよい。
ASTの構築・解析(Eclipseのプロジェクトを渡して詳しく解析する方式)
変数の参照とかメソッド呼び出しなどから、それらの型情報とか定義の取得などを考えようとすると、プログラムのソースファイル全体をASTParser
に渡してあげる必要性がでてくる。
そのための、
ASTParser.createASTs(String[] sourceFilePaths, String[] encodings, String[] bindingKeys,
FileASTRequestor requestor, IProgressMonitor monitor)
というメソッドもある。
しかし、引数の指定が面倒くさかったり、なぜかうまく動かせなかったので、
ASTParser.setProject(IJavaProject project)
というメソッドで、EclipseのJavaプロジェクト
に相当するオブジェクトIJavaProject
を渡した。
IJavaProjectの生成
IJavaProject project =JavaCore.create(ResourcesPlugin.getWorkspace().
getRoot().getProject(projectName));
ここで、ResourcesPlugin.getWorkspace()
は、Eclipseのプラグイン内でしか呼び出すことができないらしい。
そこで、アプリケーション全体をEclipseのプラグインとして実装する必要が生じる。
HeadlessなEclipseプラグイン作成
UIなしの超単純なプラグインのテンプレートとして、HeadlessHelloRCP
というものがあるので、それをベースに作成した。
その方法はこちらが詳しい。
-
Plugin-in Project
を選んでプロジェクト作成のウィザード開く -
Content
ページで- チェック外す
Generate an acivator, a Java class that ...
This plug-in will make contributions to the UI
-
Would you like ... rich client application?
をYes
にする
- チェック外す
-
Templates
ページでHeadless Hello RCP
を選ぶ
これでとりあえず、GUIなしのプラグインとして、コードを実行できるようになる。
上で挙げたプログラムは、動けば何でも良かったので、プラグインとしての体裁は全く考えなかった。