JavaParser usage memo

What is JavaParser?

--As the name implies, a library that parses Java source code --The analysis result of the source can be obtained as an abstract syntax tree (AST), and the analysis result can be accessed by a method such as the Visitor pattern. --Speaking of Java source analysis library, there is a way to use Eclipse JDT [^ 1] --JDT is primarily intended for use with Eclipse plugins, while JavaParser is purely for Java parsing. --Since the required library is one jar (about 800KB), it is easy to install. --It seems to support Java 9 --Since ʻOptional` is used for the API, it can only be used with Java 8 or above.

Hello World ** Dependencies **

build.gradle


dependencies {
    compile 'com.github.javaparser:javaparser-core:3.3.0'
}

Implementation

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/Main.java");
        try {
            CompilationUnit unit = JavaParser.parse(source);
            System.out.println("***********************************************");
            System.out.println(unit);
            System.out.println("***********************************************");
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

***********************************************
package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {

    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/Main.java");
        try {
            CompilationUnit unit = JavaParser.parse(source);
            System.out.println("***********************************************");
            System.out.println(unit);
            System.out.println("***********************************************");
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

***********************************************

Description

--You can parse Java source code with the parse () method of JavaParser --The analysis result is returned as CompilationUnit. --CompilationUnit inherits from the Node class

VoidVisitor Implementation

MyVoidVisitor.java


package sample.javaparser;

import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

public class MyVoidVisitor extends VoidVisitorAdapter<String> {

    @Override
    public void visit(VariableDeclarator n, String arg) {
        System.out.println("n > " + n);
        System.out.println("arg > " + arg);
        super.visit(n, arg);
    }
}

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/Main.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);
            
            unit.accept(new MyVoidVisitor(), "ARG!!");
            
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

n > source = Paths.get("src/main/java/sample/javaparser/Main.java")
arg > ARG!!
n > unit = JavaParser.parse(source)
arg > ARG!!

Description

MyVoidVisitor.java


import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

public class MyVoidVisitor extends VoidVisitorAdapter<String> {

    @Override
    public void visit(VariableDeclarator n, String arg) {
        System.out.println("n > " + n);
        System.out.println("arg > " + arg);
        super.visit(n, arg);
    }

--The VoidVisitor interface is provided as a Visitor for traversing the syntax tree. --In VoidVisitor, a little less than 100 methods are declared to be called back for each node type. --The VoidVisitorAdapter class is provided as an implementation that implements all of these and simply recursively scans the nodes. --Inherit this and create your own Visitor --Override the method that receives the node you want to process as necessary and execute the process --Finally, by calling super.visit (), scanning can be continued for the child node.

Main.java


            CompilationUnit unit = JavaParser.parse(source);
            
            unit.accept(new MyVoidVisitor(), "ARG!!");

--Visitor can be used by passing it to ʻaccept ()of theCompliationUnit class. --The ʻaccept () method is defined in the Visitable interface, and the CompliationUnit implements it. --By the way, there is also a way to pass the node you want to scan to the visit () method of VoidVisitor, but even if you use ʻaccept (), this visit ()` method is called after all and it works. Will be the same

Compliation Unit accept()Method implementation


    @Override
    public <A> void accept(VoidVisitor<A> v, A arg) {
        v.visit(this, arg); //★ Void Visitor visit()Is called as it is
    }

--If you pass a value to the second argument of the ʻaccept ()(andvisit ()`) method, that value will continue to work together while traversing the node. -Maybe for linking data and processing delegation destination objects that you want to refer to in common?

GenericVisitor Implementation

MyGenericVisitor.java


package sample.javaparser;

import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.visitor.GenericVisitorAdapter;

public class MyGenericVisitor extends GenericVisitorAdapter<Integer, String> {
    
    @Override
    public Integer visit(VariableDeclarator n, String arg) {
        System.out.println(arg);
        System.out.println(n);
        super.visit(n, arg);
        return -1;
    }
}

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/Main.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);
            
            int result = unit.accept(new MyGenericVisitor(), "ARG!!");
            System.out.println(result);
            
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

ARG!!
source = Paths.get("src/main/java/sample/javaparser/Main.java")
-1

Description

MyGenericVisitor.java


import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.visitor.GenericVisitorAdapter;

public class MyGenericVisitor extends GenericVisitorAdapter<Integer, String> {
    
    @Override
    public Integer visit(VariableDeclarator n, String arg) {
        System.out.println(arg);
        System.out.println(n);
        super.visit(n, arg);
        return -1;
    }

--VoidVisitor'svisit ()has no return value, but GenericVisitor's visit () has a return value. --The GenericVisitorAdaptor, an abstract class that implements GenericVisitor, will ** interrupt the scan ** and call the caller if the visit () method returns a value other than null during the scan. Returns the returned value --The second argument of visit () is treated the same as VoidVisitor.

Node type

The nodes included in the abstract syntax tree inherit from the Node class and have the following inheritance relationship.

Node
┣━ArrayCreationLevel
┣━CatchClause
┣━CompilationUnit
┣━ImportDeclaration
┣━MemberValuePair
┣━ModuleDeclaration
┣━Name
┣━PackageDeclaration
┣━Parameter
┣━SimpleName
┣━VariableDeclarator
┃ 
┣━BodyDeclaration
┃ ┣━AnnotationMemberDeclaration
┃ ┣━EnumConstantDeclaration
┃ ┣━FieldDeclaration
┃ ┣━InitializerDeclaration
┃ ┣━CallableDeclaration
┃ ┃ ┣━ConstructorDeclaration
┃ ┃ ┗━MethodDeclaration
┃ ┗━TypeDeclaration
┃   ┣━AnnotationDeclaration
┃   ┣━ClassOrInterfaceDeclaration
┃   ┗━EnumDeclaration
┃ 
┣━Comment
┃ ┣━BlockComment
┃ ┣━JavadocComment
┃ ┗━LineComment
┃ 
┣━Expression
┃ ┣━ArrayAccessExpr
┃ ┣━ArrayCreationExpr
┃ ┣━ArrayInitializerExpr
┃ ┣━AssignExpr
┃ ┣━BinaryExpr
┃ ┣━CastExpr
┃ ┣━ClassExpr
┃ ┣━ConditionalExpr
┃ ┣━EnclosedExpr
┃ ┣━FieldAccessExpr
┃ ┣━InstanceOfExpr
┃ ┣━LambdaExpr
┃ ┣━MethodCallExpr
┃ ┣━MethodReferenceExpr
┃ ┣━NameExpr
┃ ┣━ObjectCreationExpr
┃ ┣━SuperExpr
┃ ┣━ThisExpr
┃ ┣━TypeExpr
┃ ┣━UnaryExpr
┃ ┣━VariableDeclarationExpr
┃ ┣━AnnotationExpr
┃ ┃ ┣━MarkerAnnotationExpr
┃ ┃ ┣━NormalAnnotationExpr
┃ ┃ ┗━SingleMemberAnnotationExpr
┃ ┗━LiteralExpr
┃   ┣━BooleanLiteralExpr
┃   ┣━NullLiteralExpr
┃   ┗━LiteralStringValueExpr
┃     ┣━CharLiteralExpr
┃     ┣━DoubleLiteralExpr
┃     ┣━IntegerLiteralExpr
┃     ┣━LongLiteralExpr
┃     ┗━StringLiteralExpr
┃ 
┣━ModuleStmt
┃ ┣━ModuleExportsStmt
┃ ┣━ModuleOpensStmt
┃ ┣━ModuleProvidesStmt
┃ ┣━ModuleRequiresStmt
┃ ┗━ModuleUsesStmt
┃ 
┣━Statement
┃ ┣━AssertStmt
┃ ┣━BlockStmt
┃ ┣━BreakStmt
┃ ┣━ContinueStmt
┃ ┣━DoStmt
┃ ┣━EmptyStmt
┃ ┣━ExplicitConstructorInvocationStmt
┃ ┣━ExpressionStmt
┃ ┣━ForeachStmt
┃ ┣━ForStmt
┃ ┣━IfStmt
┃ ┣━LabeledStmt
┃ ┣━LocalClassDeclarationStmt
┃ ┣━ReturnStmt
┃ ┣━SwitchEntryStmt
┃ ┣━SwitchStmt
┃ ┣━SynchronizedStmt
┃ ┣━ThrowStmt
┃ ┣━TryStmt
┃ ┣━UnparsableStmt
┃ ┗━WhileStmt
┃ 
┗━Type
 ┣━IntersectionType
 ┣━PrimitiveType
 ┣━UnionType
 ┣━UnknownType
 ┣━VoidType
 ┣━WildcardType
 ┗━ReferenceType
   ┣━ArrayType
   ┣━ClassOrInterfaceType
   ┗━TypeParameter

The argument of the visit () method can receive the last class of these (there is no method that receives a class in the middle of the hierarchy such as Type or ReferenceType).

You can imagine which element of the code corresponds to from the class name, but there are some things that you can not imagine, so pick up only that side and check the operation.

ArrayCreationLevel --The place where the initial size when declaring the array is specified --ʻInt [] array = new int [10]; [10] part of `

MemberValuePair --The part that specifies the argument of the annotation with a name --@SomeAnnotation (value =" text ") value =" text " part of `

AnnotationMemberDeclaration

@interface MyAnnotation {
    String value() default "text"; //With here
    int number();                  //About here
}

ConditionalExpr --Ternary operator

EnclosedExpr --The () group used in the expression

ExplicitConstructorInvocationStmt --this () and super () that are explicitly called in the constructor

UnionType --The part of the catch clause that catchs multiple exception types --} catch (OneException | OtherException e) {

UnknownType --Null object representing a parameter whose type declaration is omitted in the argument of the lambda expression --Type of line of.forEach (line-> ...)

MarkerAnnotationExpr, NormalAnnotationExpr, SingleMemberAnnotationExpr

Those who looked up but didn't understand

Get comments

Line comments and block comments are treated differently from other normal Java syntax.

ParseCommentSample.java


package sample.javaparser;

// a comment

// b comment
public class ParseCommentSample {
    
    // c comment
    
    private String name; // d comment
}

The class to be analyzed. Try parsing this class to get comments.

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.comments.LineComment;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/ParseCommentSample.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);
            
            unit.accept(new VoidVisitorAdapter<Void>() {
                @Override
                public void visit(LineComment n, Void arg) {
                    System.out.println(n);
                    super.visit(n, arg);
                }
            }, null);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

// d comment

// b comment

// a comment and // c comment were ignored.

Comments attached to the node and isolated comments

Comments are classified into one of the following:

--Comments attached to the node --Isolated comment

Implement VoidVisitor as follows and check the operation.

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/ParseCommentSample.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);

            System.out.println("unit orphan comment > " + unit.getOrphanComments());

            unit.accept(new VoidVisitorAdapter<Void>() {
                @Override
                public void visit(ClassOrInterfaceDeclaration n, Void arg) {
                    System.out.println("<<ClassOrInterfaceDeclaration>>");
                    System.out.println("comment > " + n.getComment());
                    System.out.println("orphan comment > " + n.getOrphanComments());
                    super.visit(n, arg);
                }

                @Override
                public void visit(FieldDeclaration n, Void arg) {
                    System.out.println("<<FieldDeclaration>>");
                    System.out.println("comment > " + n.getComment());
                    super.visit(n, arg);
                }
            }, null);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

unit orphan comment > [// a comment
]

<<ClassOrInterfaceDeclaration>>
comment > Optional[// b comment
]
orphan comment > [// c comment
]

<<FieldDeclaration>>
comment > Optional[// d comment
]

--Node hasgetComment ()andgetOrphanComment (), which are methods for getting the comment associated with the node. --If a comment is adjacent to any node, the comment is treated as attached to that node. --In that case, the comment can be obtained from the attached node with getComment (). --On the other hand, comments that are not associated with any node are treated as ** isolated comments **. --Isolated comments can be obtained with getOrphanComment () --The visit () method that receives LineComment and BlockComment is called only for comments attached to some node. --If you want to handle orphaned comments, you can use getOrphanComment () or getAllContainedComments () to get all the comments.

By the way, since there is only one comment associated with one node (getComment () returns only a single comment),

// a comment
String s = "xxx"; // b comment

If it looks like, // b comment is treated as a comment associated with the variable declaration, and // a comment is an isolated comment (such a rule).

Output as a character string

ToStringSample.java


package sample.javaparser;

/**
 * Javadoc
 */
public class ToStringSample {
    // line comment

    /**
     * Javadoc
     * @param age age
     * @param name name
     */
    public void method(
       int age,
       String name
    ) {
        /* block comment */
        System.out.println(
            "age=" + age +
            "name=" + name
        );
    }
}

The format is characterized by intentionally inserting line breaks.

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/ToStringSample.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);
            System.out.println(unit);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

package sample.javaparser;

/**
 * Javadoc
 */
public class ToStringSample {

    // line comment
    /**
     * Javadoc
     * @param age age
     * @param name name
     */
    public void method(int age, String name) {
        /* block comment */
        System.out.println("age=" + age + "name=" + name);
    }
}

Description

--Normally, if you toString () a node, it will be a string that is formatted to some extent instead of the input form. --In addition, comments are output without being erased.

Adjust the shaping method

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.printer.PrettyPrinterConfiguration;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/ToStringSample.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);
            PrettyPrinterConfiguration conf = new PrettyPrinterConfiguration();
            conf.setIndent("  ");
            conf.setPrintComments(false);
            conf.setPrintJavaDoc(false);
            conf.setEndOfLineCharacter("<new line>\n");
            
            System.out.println(unit.toString(conf));
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

package sample.javaparser;<new line>
<new line>
public class ToStringSample {<new line>
<new line>
  public void method(int age, String name) {<new line>
    System.out.println("age=" + age + "name=" + name);<new line>
  }<new line>
}<new line>

Description

--By passing PrettyPrinterConfiguration as an argument oftoString (), you can specify the formatting method when returning the syntax tree to a string.

Method Description Default
setIndent(String) Specify the character string to be used for one indent (4 spaces)
setPrintComments(boolean) Specify whether to display comments true
setPrintJavaDoc(boolean) Specify whether to display Javadoc true
setEndOfLineCharacter(String) Specify the character string to be used for line breaks System.getProperty("line.separator")

Rewrite the syntax tree

Main.java


package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.stmt.BlockStmt;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/Main.java");
        
        try {
            CompilationUnit unit = JavaParser.parse(source);

            unit.getClassByName("Main").ifPresent(mainClass -> {
                // private final String textValue;Add field
                mainClass.addField(String.class, "textValue", Modifier.PRIVATE, Modifier.FINAL);
                
                //Added constructor to initialize textValue
                ConstructorDeclaration constructor = mainClass.addConstructor(Modifier.PUBLIC);
                constructor.addParameter(String.class, "textValue"); //Constructor argument addition
                BlockStmt body = constructor.createBody(); //Added constructor body
                body.addStatement("this.textValue = textValue;");
            });

            System.out.println(unit);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }
}

Execution result

package sample.javaparser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.stmt.BlockStmt;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {

    public static void main(String[] args) {
        Path source = Paths.get("src/main/java/sample/javaparser/Main.java");
        try {
            CompilationUnit unit = JavaParser.parse(source);
            unit.getClassByName("Main").ifPresent(mainClass -> {
                // private final String textValue;Add field
                mainClass.addField(String.class, "textValue", Modifier.PRIVATE, Modifier.FINAL);
                //Added constructor to initialize textValue
                ConstructorDeclaration constructor = mainClass.addConstructor(Modifier.PUBLIC);
                //Constructor argument addition
                constructor.addParameter(String.class, "textValue");
                //Added constructor body
                BlockStmt body = constructor.createBody();
                body.addStatement("this.textValue = textValue;");
            });
            System.out.println(unit);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
    }

    private final String textValue;

    public Main(String textValue) {
        this.textValue = textValue;
    }
}

--Node provides a method to rewrite the syntax tree into any form. --The API is easy to understand if you look at the method completion of the IDE. --There are also remove methods such as remove ()

reference

[^ 1]: Analyze Java source code with JDT --Qiita

Recommended Posts

JavaParser usage memo
WatchService usage memo
PlantUML usage memo
JUnit5 usage memo
Spring Shell usage memo
Spring Security usage memo CSRF
Spring Security usage memo Run-As
Spring Security Usage memo Method security
Spring Security usage memo Remember-Me
Dependency Management Plugin Usage memo
Spring Security usage memo CORS
Spring Security usage memo test
Spring Security usage memo Authentication / authorization
JCA (Java Cryptography Architecture) Usage Memo
Spring Security usage memo response header
Spring Security usage memo session management
Spring Security usage memo Basic / mechanism
Integer memo
docker memo
Spring Security Usage Memo Domain Object Security (ACL)
Lombok memo
Dockerfile memo
Iterator memo
corretto memo
Java memo
AWS memo
Dcokerfile memo
Ruby memo
irb usage
Memo Stream