Top / Eclipse / プラグイン開発のTIPS集 / ソースコードを解析するパーサASTParser

ASTParserとは

さてさて、いままでEclipseのウィジェットとか、リスナーとか比較的IDEよりでないところを見てきましたが、今回はソースコードを解析するパーサ、ASTParserを見てみたいと思います。

ASTParserはパッケージエクスプローラなどに表示されているJavaソースからソースコードを取得し解析するとか、ユーザ入力値に応じてJavaのソースコードを生成するとか、Javaソースをプログラマブルに操作するときに使用します。

やってみる

触ってみましょう。ソースコード情報はパッケージエクスプローラから取得するとして、そのクラスを操作するリーダを作ってみました。

package nu.mine.kino.plugin.astsampless;

import java.util.Iterator;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.TagElement;

/**
 * Javaソースを解析するリーダーです。
 * 
 * @author Masatomi KINO
 * @version $Revision$
 */
public class SourceReader {

  private static final Logger logger = Logger.getLogger(SourceReader.class);

  private final ICompilationUnit element;

  public SourceReader(ICompilationUnit element) {
    this.element = element;
  }

  /**
   * 渡されたソースコードの解析を行います。
   */
  public void read() {
    logger.debug("read() - start");
    
    ASTParser parser = ASTParser.newParser(AST.JLS3);
    // parser.setResolveBindings(true);
    parser.setSource(element);
    
    CompilationUnit unit = (CompilationUnit) parser
     .createAST(new NullProgressMonitor());
    
    unit.accept(new ASTVisitorImpl());
    
    logger.debug("ファイル名: " + element.getElementName());// ファイル名
    String sourceName = element.getElementName().substring(0,
     element.getElementName().lastIndexOf("."));
    logger.debug("クラス名: " + sourceName);
    logger.debug("クラスの完全修飾クラス名: "
     + element.getType(sourceName).getFullyQualifiedName());
    logger.debug("パッケージ名: " + element.getParent().getElementName());
    
    logger.debug("read() - end");
  }

  /**
   * ソースを走査するVisitorの実装クラスです。
   * 
   * @author Masatomi KINO
   * @version $Revision$
   */
  class ASTVisitorImpl extends ASTVisitor {
    private final Logger logger = Logger.getLogger(ASTVisitorImpl.class);

    public boolean visit(Javadoc node) {
      logger.debug("visit(Javadoc) - start");
      logger.debug(node);
      Iterator iterator = node.tags().iterator();
      while (iterator.hasNext()) {
        TagElement element = (TagElement) iterator.next();
        logger.debug("型:" + element.getClass().getName());
        logger.debug("tagname: " + element.getTagName());
        logger.debug(element);
      }
      logger.debug("visit(Javadoc) - end");
      return super.visit(node);
    }
  }
}

このリーダは、コンストラクタでわたってくるJavaソースモデル(ICompilationUnit?)をフィールドに保持しておき、readメソッドでそのフィールドのモデルの解析を行います*1

ここで実際に解析を行っているのが、

  • org.eclipse.jdt.core.dom.ASTParser;
  • org.eclipse.jdt.core.dom.ASTVisitor;

です。ASTParserはJavaのソースを、コード部分やコメント部分、JavaDoc?部分や、パッケージやimportなどの要素に分解するパーサです。ASTVisitorインタフェースはいわゆるビジタパタンのビジターインタフェースです。ソースコードをASTParserが解析してくれた結果、ソースは様々な要素に分解されるのですが、このASTVisitorインタフェースはその要素ごとのvisitメソッドがあり、それらを実装することで各要素への処理を行うようになっています。具体的に上のASTVisitorの実装クラスを見てみると

class ASTVisitorImpl extends ASTVisitor {
  private final Logger logger = Logger.getLogger(ASTVisitorImpl.class);
  public boolean visit(Javadoc node) {
    logger.debug("visit(Javadoc) - start");
    logger.debug(node);
    Iterator iterator = node.tags().iterator();
    while (iterator.hasNext()) {
      TagElement element = (TagElement) iterator.next();
      logger.debug("型:" + element.getClass().getName());
      logger.debug("tagname: " + element.getTagName());
      logger.debug(element);
    }
    logger.debug("visit(Javadoc) - end");
    return super.visit(node);
  }
}

となっていて、ここではJavaDoc?要素に関する処理だけを実装してあります。

ICompilationUnit?(ソースコード)から情報を得る

また、コンストラクタで渡されるICompilationUnit?からも情報を取得することができます。

logger.debug("ファイル名: " + element.getElementName());// ファイル名
String sourceName = element.getElementName().substring(0,
element.getElementName().lastIndexOf("."));
logger.debug("クラス名: " + sourceName);
logger.debug("クラスの完全修飾クラス名: "
  + element.getType(sourceName).getFullyQualifiedName());
logger.debug("パッケージ名: " + element.getParent().getElementName());

あたりですね。elementがICompilationUnit?のインスタンスです。実行結果は以下の通り。

ファイル名: BL.java
クラス名: BL
クラスの完全修飾クラス名: nu.mine.kino.BL
パッケージ名: nu.mine.kino

ただ 完全修飾クラス名 を取得するメソッドは、そのファイルのクラス名を取得しているのではなく*2、elementのパッケージ名に引数のクラス名を連結してるだけ、、みたいです。

この辺の(ASTParserでなく)ICompilationUnit?周りは Javaプロジェクトを操作する にまとめました。

ソースを渡して、解析させてみる

ソースを渡すのは拡張ポイント使ってなんとでもできるんで後回し。以下のサンプルソースをさっきのリーダに渡して解析させてみます。

  • サンプル
    package nu.mine.kino;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * クラスコメントです。
     * クラスのコメントを記述しています。
     * @author m-kino
     * @see java.lang.String
     *
     */
    public class TestMain {
      public static void main(String[] args) {
        Date date = new Date();
        List list = null;
        System.out.println("Main.");
      }
    
      /**
       * helloメソッドのJavaDocコメントです。
       * 改行してみた
       * @return 文字列。
       * @param name 名前。
       */
      public String hello(String name) {
        System.out.println("Hello "+ name +" .");
        return "Hello "+ name +" .";
      }
    
      /*
       * hello2メソッドのJavaDocコメントです。
       * 改行してみた
       * @return 文字列。
       * @param name 名前。
       */
      public String hello2(String name) {
        System.out.println("Hello "+ name +" .");
        return "Hello "+ name +" .";
      }
    }

実行結果

実行結果は以下のようになりました。

[main] DEBUG  - visit(Javadoc) - start
[main] DEBUG  - /** 
* クラスコメントです。
* クラスのコメントを記述しています。
* @author m-kino
* @see java.lang.String
*/

[main] DEBUG  - 型:org.eclipse.jdt.core.dom.TagElement
[main] DEBUG  - tagname: null
[main] DEBUG  - 
* クラスコメントです。
* クラスのコメントを記述しています。
[main] DEBUG  - 型:org.eclipse.jdt.core.dom.TagElement
[main] DEBUG  - tagname: @author
[main] DEBUG  - 
* @author m-kino
[main] DEBUG  - 型:org.eclipse.jdt.core.dom.TagElement
[main] DEBUG  - tagname: @see
[main] DEBUG  - 
* @see java.lang.String
[main] DEBUG  - visit(Javadoc) - end
[main] DEBUG  - visit(Javadoc) - start
[main] DEBUG  - /** 
* helloメソッドのJavaDocコメントです。
* 改行してみた
* @return 文字列。
* @param name 名前。
*/

[main] DEBUG  - 型:org.eclipse.jdt.core.dom.TagElement
[main] DEBUG  - tagname: null
[main] DEBUG  - 
* helloメソッドのJavaDocコメントです。
* 改行してみた
[main] DEBUG  - 型:org.eclipse.jdt.core.dom.TagElement
[main] DEBUG  - tagname: @return
[main] DEBUG  - 
* @return 文字列。
[main] DEBUG  - 型:org.eclipse.jdt.core.dom.TagElement
[main] DEBUG  - tagname: @param
[main] DEBUG  - 
* @param name 名前。
[main] DEBUG  - visit(Javadoc) - end
[main] DEBUG nu.mine.kino.plugin.astsampless.SourceReader - execute() - end
[main] DEBUG nu.mine.kino.plugin.astsampless.SampleAction - ----------
[main] DEBUG nu.mine.kino.plugin.astsampless.SampleAction - run(IAction) - end

この結果から、以下のようになってることがわかりますね。

  • JavaDoc?ひとつが、org.eclipse.jdt.core.dom.Javadoc インスタンスになっている。
  • コメント
    /** 
     * クラスコメントです。
     * クラスのコメントを記述しています。
     * @author m-kino
     * @see java.lang.String
     */
    * クラスコメントです。
    * クラスのコメントを記述しています。
    * @author m-kino
    * @see java.lang.String
    に分割される。この分割されたオブジェクトはorg.eclipse.jdt.core.dom.Javadoc#tags() で取得可能(org.eclipse.jdt.core.dom.TagElement?を格納したListが返る)

ASTVisitorの他のメソッドもOverrideしてみる

ASTVisitorには他のメソッドたとえば

public boolean visit(ImportDeclaration node);

などOverrideできる物がたくさんあります。これらをOverrideしてどのような情報が取得できるか試してみます。

ImportDeclaration?

インポート文を表現するクラスですね。

 public boolean visit(ImportDeclaration node) {
   logger.debug("ImportDeclaration: " + node.getName());
   return super.visit(node);
 }

の実行結果は以下の通り。

[main] DEBUG - ImportDeclaration: java.util.Date
[main] DEBUG - ImportDeclaration: java.util.List

一つのimport文に対して一回 public boolean visit(ImportDeclaration? node) が呼ばれるんですね。ちなみに

logger.debug("ImportDeclaration: " + node);

とするとimport xxxx; のimportまで取得可能です。

PackageDeclaration?

パッケージを表現するクラスですね。 実行結果は以下の通り。

[main]  - PackageDeclaration: nu.mine.kino

パッケージ名が取得できました。 ちなみに

logger.debug("PackageDeclaration: " + node);

とするとpackage nu.mine.kino; のpackageまで取得可能です。

コンテンツ一覧

関連リンク


この記事は

選択肢 投票
おもしろかった 8  
そうでもない 3  

Top / Eclipse / プラグイン開発のTIPS集 / ソースコードを解析するパーサASTParser

現在のアクセス:34351


*1 コンストラクタで渡される場合ってのはパッケージ・エクスプローラのSelectionなどから渡されるとか。テキスト文字列や、ファイルを直接読み込んで云々とかの場合は、parser.setSource(char[] chars);を使用します。char[]はString#toCharArray?()などで生成します。
*2 引数にクラス名渡してるくらいだから

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2015-02-10 (火) 20:23:05 (839d)