--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 the
CompliationUnit 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 ()(and
visit ()`) 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
.
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 catch
s 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
MarkerAnnotationExpr
--A place where it is used without using any attributes, such as @Override
NormalAnnotationExpr
--A place where the attribute value is set by specifying the attribute name, such as @SuppressWarnings (value =" unused ")
SingleMemberAnnotationExpr
--A place where the attribute name is omitted for a single value
, such as@SuppressWarnings ("unused")
Name
SimpleName
UnparsableStmt
IntersectionType
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 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).
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.
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") |
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 ()
[^ 1]: Analyze Java source code with JDT --Qiita
Recommended Posts