EclipseJDTのASTParserでJavaソースコードの抽象構文木とかHeadlessなEclipseプラグインとか

*WARNING: この記事の内容は、2014/02に記述したメモです。内容が古くなっていますので、ご注意ください。*
*あと、間違っている記述があるかもしれません。。。*

# 概要

Eclipseの内部で使われているJavaのASTパーサー (`org.eclipse.jdt.core.dom.ASTParser`) を使って、Javaのソースコードを解析してみた話。大学時代に弄ってたもの。

正直なところ、この記事の内容はこちらのページにのほうが詳しい。

* [JDT AST](http://sdl.ist.osaka-u.ac.jp/~s-kimura/jdtdoc/jdt.html)

# 何ができたか

つくったもの: [headless](https://github.com/hiraro/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](https://github.com/hiraro/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`というものがあるので、それをベースに作成した。
その方法はこちらが詳しい。

* [Eclipseを使ったheadless applicationの作成方法 - ブログ::=（メモ｜落書き）帳](http://radi0ant.hatenablog.com/entry/2013/03/21/011832)
* [Code & Me: Creating a headless application](http://codeandme.blogspot.jp/2012/02/creating-headless-application.html)

![20140221170911.png](https://qiita-image-store.s3.amazonaws.com/0/6379/23e69b42-7403-2519-f21f-e7ecb5206d9c.png)

![20140221170915.png](https://qiita-image-store.s3.amazonaws.com/0/6379/6f1254a0-fcda-a84c-7c00-8f2ed850abd1.png)

* `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なしのプラグインとして、コードを実行できるようになる。
上で挙げたプログラムは、動けば何でも良かったので、プラグインとしての体裁は全く考えなかった。