前回、第1回 JavaParserを使ってみた(Javaソースコードを生成しよう)で、ソースコードを生成したが、今回は、ソースコードを読み込んで、いじって改変したいと思います。
開発環境
事前準備
プロジェクトを作成して、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(); } |
ArrayAccessExpr | getNames()[15*15] |
ArrayCreationExpr | new int[5] |
ArrayCreationLevel | new int[1][2] |
ArrayInitializerExpr | new int[][] {{1, 1}, {2, 2}} |
ArrayType | int[][] |
AssertStmt | assert |
AssignExpr | a=5 |
BinaryExpr | a && b |
BlockComment | /* My Comment */ |
BlockStmt | { … } |
BooleanLiteralExpr | true false |
BreakStmt | break |
CastExpr | (long)15 |
CatchClause | catch (Exception e) { … } |
CharLiteralExpr | ‘a’ |
ClassExpr | Object.class |
ClassOrInterfaceDeclaration | class X { … } |
ClassOrInterfaceType | Object HashMap<String, String> java.util.Punchcard |
CompilationUnit | A .java File |
ConditionalExpr | if(a) |
ConstructorDeclaration | X { X() { } } |
ContinueStmt | continue |
DoStmt | do { … } while ( a==0 ) |
DoubleLiteralExpr | 100.1f |
EmptyMemberDeclarationclass | X { ; } |
EnclosedExpr | (1+1) |
EnumConstantDeclaration | X { A(1), B(2) } |
EnumDeclaration | enum X { … } |
ExplicitConstructorInvocationStmt | class X { X() { super(15); } } class X { X() { this(1, 2); } } |
FieldAccessExpr | person.name |
FieldDeclaration | private static int a=15 |
ForeachStmt | for(Object o: objects) { … } |
ForStmt | for(int a=3,b=5; a>99; a++) { … } |
IfStmt | if(a==5) hurray() else boo() |
ImportDeclaration | import com.github.javaparser.JavaParser |
InitializerDeclaration | class X { static { a=3; } } |
InstanceOfExpr | tool instanceof Drill |
IntegerLiteralExpr | 8934 |
IntersectionType | Serializable & Cloneable |
JavadocComment | /** a comment */ |
LabeledStmt | label123: println(“continuing”) |
LambdaExpr | (a, b) -> a+b |
LineComment | // Comment |
LocalClassDeclarationStmt | class X { void m() { class Y { } } } |
LongLiteralExpr | 8934l |
MarkerAnnotationExpr | @Override |
MemberValuePair | @Counters(a=15) |
MethodCallExpr | circle.circumference() |
MethodDeclaration | public int abc() {return 1;} |
MethodReferenceExpr | System.out::println |
NameExpr | int x = a + 3 |
NormalAnnotationExpr | @Mapping(…) |
NullLiteralExpr | null |
ObjectCreationExpr | new HashMap.Entry(15) |
PackageDeclaration | package com.github.javaparser.ast |
Parameter | int abc(String x) |
PrimitiveType | int |
ReturnStmt | return 5 * 5 |
SingleMemberAnnotationExpr | @Count(15) |
StringLiteralExpr | “Hello World!” |
SuperExpr | super |
SwitchEntryStmt | case 1: |
SwitchStmt | switch(a) { … } |
SynchronizedStmt | synchronized (a123) { … } |
ThisExpr | this |
ThrowStmt | throw new Exception() |
TryStmt | try ( … ) { … } catch ( … ) { } finally { } |
TypeExpr | World::greet |
TypeParameter | <U> U getU() { … } |
UnaryExpr | 11++ |
UnionType | catch(IOException NullPointerException ex) |
UnknownType | DoubleToIntFunction d = x -> (int)x + 1 |
VariableDeclarationExpr | final int x=3, y=55 |
VariableDeclarator | int x = 14 |
VoidType | void helloWorld() { … } |
WhileStmt | while(true) { … } |
WildcardType | Collection<?> c |
コメント