LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

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というものがあるので、それをベースに作成した。
その方法はこちらが詳しい。

20140221170911.png

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1