第1回 JavaParserを使ってみた(Javaソースコードを生成しよう)

ブログ
この記事は約11分で読めます。

JavaParserとは

Javaのソースコードを構文解析するライブラリで、コードを抽象構文木(AST)に変換してくれます。解析結果の抽象構文木に対して、追加、削除も可能です。もちろん、解析結果をソースコードに変換することも可能です。
また、自分で抽象構文木を作成することも可能なので、独自のコード生成ツールも作成することもできます。

今回は、自分で抽象構文木を作成して、コード生成をしてみます。

開発環境

2021/3/7時点
  • Windows10 Home
  • Java v1.0.8.10 (AdoptOpenJDK)
  • Gradle v6.8.3
  • VSCode(Visual Studio Code)
  • javaparser 3.19

プロジェクト作成とVSCode

コマンドプロンプトでプロジェクトを作成します。gradle initの選択は、すべてデフォルトです。

>cd c:\
>mkdir c:\work\mygenerator
>cd c:\work\mygenerator
>gradle init --type java-application
>code .
>rem テストクラスは、本質でないので削除
>del app\src\test\java\mygenerator\AppTest.java

Gradleの設定

c:\work\mygenerator\app\build.gradleのdependenciesにJavaParserライブラリを追加します。最新バージョンは、Maven Repositoryを参照してください。

dependencies {
    …
    implementation 'com.github.javaparser:javaparser-core:3.19.0'
}

コード生成

main関数だけがあるクラスのコード生成をしてみましょう。
説明は後でするとして、以下がそのコードです。

package mygenerator;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier.Keyword;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;

public class App {
    public static void main(String[] args) {
        CompilationUnit compilationUnit = new CompilationUnit(); // 出力する、1ファイルに相当するクラス
        compilationUnit.setPackageDeclaration("javaparser"); // パッケージの設定
        ClassOrInterfaceDeclaration classOrInterfaceDeclaration = compilationUnit.addClass("App", Keyword.PUBLIC); // クラスの設定
        MethodDeclaration methodDeclaration  = classOrInterfaceDeclaration.addMethod("main", Keyword.PUBLIC); // mainクラスの設定
        methodDeclaration.setStatic(true); // main関数にstaticを追加
        methodDeclaration.addParameter(String[].class, "args"); // main関数に「String[] args」パラメータ追加

        // compilationUnitのtoStringを実行するとソースコードが文字列として戻る
        System.out.println(compilationUnit.toString());

    }
}

結果は、以下になります。コンソールに出力されるます。

package javaparser;

public class App {
    public static void main(String[] args) {
    }
}

説明

JavaParserを使ったコードを説明します。

CompilationUnitクラス

コンパイル単位、Javaでの1ファイルに相当するクラスです。こちらが抽象構文木のルートになります。

CompilationUnit compilationUnit = new CompilationUnit();

JavaParserはNodeを継承して、ツリーを形成しています。このクラスに他のノードを追加して、最終的に#toStringにより、コード変換をします。
CompilationUnitクラスは、package, import, class, interface, enumなどがadd***関数で追加できるようになっています。

PackageDeclaration

パッケージを構成するクラスです。compilationUnit.setPackageDeclarationの内部で、PackageDeclarationをインスタンス化して登録しています。

compilationUnit.setPackageDeclaration("javaparser");
public CompilationUnit setPackageDeclaration(String name) {
    setPackageDeclaration(new PackageDeclaration(parseName(name)));
    return this;
}

PackageDeclarationは、コンストラクタでパッケージ名を入れることができます。「javaparser」がパッケージ名です。
もちろん、直接インスタンス化させて、CompilationUnitに設定することも可能です。

ClassOrInterfaceDeclaration

ClassOrInterfaceDeclarationは、クラス、もしくは、インターフェースを構成するクラスです。クラス名、アクセス修飾子やアノテーションなどを設定できます。
今回は、publicでAppという名前のクラスを、compilationUnit.addClassの内部で追加しています。

ClassOrInterfaceDeclaration classOrInterfaceDeclaration = compilationUnit.addClass("App", Keyword.PUBLIC); // クラスの設定
public ClassOrInterfaceDeclaration addClass(String name, Modifier.Keyword... modifiers) {
    ClassOrInterfaceDeclaration classOrInterfaceDeclaration = new ClassOrInterfaceDeclaration(createModifierList(modifiers), false, name);
    getTypes().add(classOrInterfaceDeclaration);
    return classOrInterfaceDeclaration;
}

MethodDeclaration

MethodDeclarationは、関数を構成するクラスです。関数名や引数、アクセス修飾子、アノテーションなどが追加できます。classOrInterfaceDeclaration.addMethodの内部で、MethodDeclarationが追加されます。

MethodDeclaration methodDeclaration  = classOrInterfaceDeclaration.addMethod("main", Keyword.PUBLIC); // mainクラスの設定
default MethodDeclaration addMethod(String methodName, Keyword... modifiers) {
    MethodDeclaration methodDeclaration = new MethodDeclaration();
    methodDeclaration.setName(methodName);
    methodDeclaration.setType(new VoidType());
    methodDeclaration.setModifiers(createModifierList(modifiers));
    getMembers().add(methodDeclaration);
    return methodDeclaration;
}

今回は、main関数の追加です。このままだと、void main()が作成されるので、static宣言や引数を別途、設定しています。

methodDeclaration.setStatic(true);
methodDeclaration.addParameter(String[].class, "args");

出力

各クラスは、toStringにより、配下のNodeに対してコードを出力させることができます。

System.out.println(compilationUnit.toString());

上記は、compilationUnitに設定されたパッケージ、クラス、関数のコードを文字列として出力されるようになっています。

また、YamlPrinterクラス、XmlPrinterクラスを使用することで、yaml形式、xml形式も出力できるようになっています。

import com.github.javaparser.printer.XmlPrinter;
import com.github.javaparser.printer.YamlPrinter;
…
System.out.println(new YamlPrinter(true).output(compilationUnit));
System.out.println(new XmlPrinter(true).output(compilationUnit));
root(Type=CompilationUnit):
    packageDeclaration(Type=PackageDeclaration):
        name(Type=Name):
            identifier: "javaparser"
    types:
        - type(Type=ClassOrInterfaceDeclaration):
            isInterface: "false"
root(Type=CompilationUnit):
    packageDeclaration(Type=PackageDeclaration):
        name(Type=Name):
            identifier: "javaparser"
    types:
        - type(Type=ClassOrInterfaceDeclaration):
            isInterface: "false"
            name(Type=SimpleName):
                identifier: "App"
            members:
                - member(Type=MethodDeclaration):
                    body(Type=BlockStmt):
                    type(Type=VoidType):
                    name(Type=SimpleName):
                        identifier: "main"
…
<root type='CompilationUnit'>
    <packageDeclaration type='PackageDeclaration'>
        <name type='Name' identifier='javaparser'></name>
    </packageDeclaration>
    <types>
        <type type='ClassOrInterfaceDeclaration' isInterface='false'>
            <name type='SimpleName' identifier='App'></name>
            <members>
                <member type='MethodDeclaration'>
                    <body type='BlockStmt'></body>
                    <type type='VoidType'></type>
                    <name type='SimpleName' identifier='main'></name>
…

まとめ

JavaParserを使って、簡単なクラスを出力することができました。
実際、触ってみた感じでは、対応するクラスとインスタンス化させる方法を探すのが大変でした。別途、説明する予定のソースコードからNodeに変換する方法を利用して、対象クラスを探したり、内部コードを見て判断して今回のコードを作成しました。自分が出力したいコードを前もって準備しておいたほうがやりやすいです。

コメント