第2回 JavaParserを使ってみた(ソースコードから抽象構文木に変換して変更してみよう)

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

前回、第1回 JavaParserを使ってみた(Javaソースコードを生成しよう)で、ソースコードを生成したが、今回は、ソースコードを読み込んで、いじって改変したいと思います。

開発環境

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

事前準備

プロジェクトを作成して、VSCodeを表示するまでをコマンドプロンプトで実行します。

>mkdir c:\work\visitor
>cd c:\work\visitor
>c:\work\visitor>gradle init --type java-application
>code .

app\build.gradleにJavaParserの依存関係を追加

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

変更前のソースコードが必要なので、resourcesフォルダに、以下のコードを保存(App.txt)しておきます。
絶対パス:C:\work\visitor\app\src\main\resources\App.txt

package visitor
;

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

JavaParserについて

JavaParserは、解析をサポートするクラスがあります。CompilationUnitは、第1回でNodeのルートに位置するクラスですが、StatiscJavaParser(JavaParser)は、コード解析を行います。

また、Visitorクラスは、特定の種類のNodeを見つけるためのクラスです。今回は、Visitorクラスを使って変更してみましょう

変更してみよう

既存のNodeを変更したいときに、ModifierVisitorクラスを使用すると特定の種類のノードに対して修正ができるので、便利です。

このクラスは、各種類のノードに対応する関数が用意されており、その関数をオーバーライドして、ノードの値を変更したり、nullを返却して削除したりできます。ノードなので、親や子供のノードに対しても変更可能です。

Visitorクラスを使って、クラス名を変更してみる

main関数を修正して、基になるクラスのテキストファイルの読み込みと変更処理を入れます。変更処理は、ClassVisitorクラスの作成してその中にいれます。

package visitor;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.visitor.ModifierVisitor;
import com.github.javaparser.ast.visitor.Visitable;

public class App {
    public static void main(String[] args) {
        InputStream is  = App.class.getClassLoader().getResourceAsStream("App.txt");
        CompilationUnit cu = StaticJavaParser.parse(is); // 基となるクラスをノードに変換

        cu.accept(new ClassVisitor(), null); // クラス名を変更するための呼び出し
        cu.setStorage(Paths.get("app/src/main/java/visitor/App2.java"), StandardCharsets.UTF_8);
        cu.getStorage().ifPresent(f -> f.save()); // App2を保存
    }

    /**
     * App2にクラス名を変更
     */
    public static class ClassVisitor extends ModifierVisitor<Void> {
        @Override
        public Visitable visit(ClassOrInterfaceDeclaration n, Void arg) {
            n.setName("App2");
            return super.visit(n, arg);
        }
    }
}

重要なのは、ClassVisitorクラスとmain関数にあるCompilationUnit#accept関数です。


ClassVisitorは、ModifierVisitorを継承しています。ModifierVisitor#visit(ClassOrInterfaceDeclaration)関数は、ClassOrInterfaceDeclarationノードが探索されたときに呼ばれる関数です。その関数をオーバーライドして、setName(クラス名)により、クラス名を変更しています。

CompilationUnit#acceptは、実行したいVisitorクラスのインスタンスとそのインスタンスに渡したい値があればそれを入れます。今回は、何も渡さないので、nullを第2引数に入れています。

ノードを使って、クラス名を変更してみる

今度は、ノードを使って、クラス名を変更させます。単純な処理なら、Visitorクラスを使わなくても変更できます。

findFirstでノードの検索をして初めに見つかった対象ノードをOptionalで取得します。引数は、検索したいノードクラスです。ClassOrInterfaceDeclarationを指定しているので、初めに見つかったクラスまたは、インターフェースになります。基になるファイルがクラス1つなのでこのような検索方法になっています。必要であれば、findAllですべて取得することも可能です。

package visitor;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;

public class App {
    public static void main(String[] args) {
        InputStream is = App.class.getClassLoader().getResourceAsStream("App.txt");
        CompilationUnit cu = StaticJavaParser.parse(is);

        cu.findFirst(ClassOrInterfaceDeclaration.class).ifPresent(f -> f.setName("App2"));


        cu.setStorage(Paths.get("app/src/main/java/visitor/App2.java"), StandardCharsets.UTF_8);
        cu.getStorage().ifPresent(f -> f.save()); // App2
    }
}

このように、対象ノードを検索して取得するか、childNodesで子ノードを順次探索して対象を見るけるか、それぞれ探索しやすい方法で処理をするといいでしょう。

まとめ

Visitorクラスとノード探索によるクラス名変更処理を書いてみました。簡単な処理であれば、ノード探索による変更でいいと思います。ただ、Visitorクラスを使うと、探索・変更をまとめることができるので、少し大きなプログラムを書く場合は、こちらをお勧めします。

Tips:各種コードと対応するクラス

AnnotationDeclaration@interface X { … }
AnnotationMemberDeclaration@interface X { int id(); }
ArrayAccessExprgetNames()[15*15]
ArrayCreationExprnew int[5]
ArrayCreationLevelnew int[1][2]
ArrayInitializerExprnew int[][] {{1, 1}, {2, 2}}
ArrayTypeint[][]
AssertStmtassert
AssignExpra=5
BinaryExpra && b
BlockComment/* My Comment */
BlockStmt{ … }
BooleanLiteralExprtrue
false
BreakStmtbreak
CastExpr(long)15
CatchClausecatch (Exception e) { … }
CharLiteralExpr‘a’
ClassExprObject.class
ClassOrInterfaceDeclarationclass X { … }
ClassOrInterfaceTypeObject
HashMap<String, String>
java.util.Punchcard
CompilationUnitA .java File
ConditionalExprif(a)
ConstructorDeclarationX { X() { } }
ContinueStmtcontinue
DoStmtdo { … } while ( a==0 )
DoubleLiteralExpr100.1f
EmptyMemberDeclarationclassX { ; }
EnclosedExpr(1+1)
EnumConstantDeclarationX { A(1), B(2) }
EnumDeclarationenum X { … }
ExplicitConstructorInvocationStmtclass X { X() { super(15); } }
class X { X() { this(1, 2); } }
FieldAccessExprperson.name
FieldDeclarationprivate static int a=15
ForeachStmtfor(Object o: objects) { … }
ForStmtfor(int a=3,b=5; a>99; a++) { … }
IfStmtif(a==5) hurray() else boo()
ImportDeclarationimport com.github.javaparser.JavaParser
InitializerDeclarationclass X { static { a=3; } }
InstanceOfExprtool instanceof Drill
IntegerLiteralExpr8934
IntersectionTypeSerializable & Cloneable
JavadocComment/** a comment */
LabeledStmtlabel123: println(“continuing”)
LambdaExpr(a, b) -> a+b
LineComment// Comment
LocalClassDeclarationStmtclass X { void m() { class Y { } } }
LongLiteralExpr8934l
MarkerAnnotationExpr@Override
MemberValuePair@Counters(a=15)
MethodCallExprcircle.circumference()
MethodDeclarationpublic int abc() {return 1;}
MethodReferenceExprSystem.out::println
NameExprint x = a + 3
NormalAnnotationExpr@Mapping(…)
NullLiteralExprnull
ObjectCreationExprnew HashMap.Entry(15)
PackageDeclarationpackage com.github.javaparser.ast
Parameterint abc(String x)
PrimitiveTypeint
ReturnStmtreturn 5 * 5
SingleMemberAnnotationExpr@Count(15)
StringLiteralExpr“Hello World!”
SuperExprsuper
SwitchEntryStmtcase 1:
SwitchStmtswitch(a) { … }
SynchronizedStmtsynchronized (a123) { … }
ThisExprthis
ThrowStmtthrow new Exception()
TryStmttry ( … ) { … } catch ( … ) { } finally { }
TypeExprWorld::greet
TypeParameter<U> U getU() { … }
UnaryExpr11++
UnionTypecatch(IOException NullPointerException ex)
UnknownTypeDoubleToIntFunction d = x -> (int)x + 1
VariableDeclarationExprfinal int x=3, y=55
VariableDeclaratorint x = 14
VoidTypevoid helloWorld() { … }
WhileStmtwhile(true) { … }
WildcardTypeCollection<?> c

コメント