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

  • 0
    いいね
  • 0
    コメント

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