0% found this document useful (0 votes)
35 views44 pages

JavaParser JUG Milano

JavaParser is a mature tool for parsing, modifying, and analyzing Java code, capable of generating an Abstract Syntax Tree (AST) and converting it back to code. JavaSymbolSolver complements JavaParser by resolving symbols within the AST, providing additional context for method calls and type resolutions. Together, they enable advanced code analysis and automated refactoring, making them valuable for developers working with Java codebases.

Uploaded by

jingengliu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views44 pages

JavaParser JUG Milano

JavaParser is a mature tool for parsing, modifying, and analyzing Java code, capable of generating an Abstract Syntax Tree (AST) and converting it back to code. JavaSymbolSolver complements JavaParser by resolving symbols within the AST, providing additional context for method calls and type resolutions. Together, they enable advanced code analysis and automated refactoring, making them valuable for developers working with Java codebases.

Uploaded by

jingengliu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

JavaParser

per generare, modificare e analizzare codice Java

Federico Tomassetti
The JavaParser family

JavaParser JavaSymbolSolver
a.k.a. JP a.k.a. JSS
Is this stuff mature?

JavaParser is a project with a long history, contributions from over 50


persons, and basically it works.

JavaSymbolSolver is much younger and it works decently enough.


Until it does not.
Yes, it supports all of Java
Even the crazy things you all forgot about…
Yes, it supports all of Java
Even the crazy things no one actually used…
What JavaParser does?

JavaParser… parse Java code into a Java AST

package [Link];
CompilationUnit
class A {
int field;
} PackageDecl ClassDecl

FieldDecl

PrimitiveType
What JavaParser does?

JavaParser unparse an AST into code

CompilationUnit

package [Link];
PackageDecl ClassDecl
class A {
int field;
FieldDecl }

PrimitiveType
Hello, JavaParser!

// Get a compilation unit


[Link](myFile)
[Link](code)

// Or an expression
[Link](”1 + 2”)

// Or a statement
[Link](”if (a) b = 1;”)
Isn’t JP enough?

int foo;

public void aMethod(int foo) {


foo = 1;
}

public void anotherMethod() {


foo = 1;
}

To JP these two statements looks the same: they produce the same
AST nodes.

It is the assignment of a thing named ”foo”, no idea what that thing is


Isn’t JP enough?

public void print1(String foo) {


[Link](foo);
}

public void print2(int foo) {


[Link](foo);
}

To JP these two statements looks the same: they produce the same
AST nodes.

It is the call of a method named “print”, no idea which signature that


has
Isn’t JP enough?

class A { }

public void creator1() {


new A();
}

public void creator2() {


class A { }
new A();
}

To JP these two statements looks the same: they produce the same
AST nodes.

It is the instantiation of a class named “A”, no idea where it is defined


What JavaSymbolSolver does?

JavaSymbolSolver resolves symbols in the JavaParser AST

package [Link];
CompilationUnit
class C {
D field;
} PackageDecl ClassDecl

FieldDecl

ReferenceType
package [Link];

class D {
}
Relationship JP & JSS

Certain methods in the AST requires additional intelligence.

[Link]();

JSS not enabled JSS enabled

[Link]();

JSS not enabled JSS enabled


Hello, JavaSymbolSolver!

// 1) Prepare JavaParser
TypeSolver typeSolver = /* configure where to look */;
ParserConfiguration parserConfiguration =
new ParserConfiguration().setSymbolResolver(
new JavaSymbolSolver(typeSolver));
JavaParser parser = new JavaParser(parserConfiguration);

// 2) Parse using the advanced API


CompilationUnit compilationUnit =
[Link](ParseStart.COMPILATION_UNIT,
new StreamProvider(new FileInputStream(myFile)))
.getResult().get();

// 3) Use the AST… with some extra functionalities


Hello, JavaSymbolSolver!

CompilationUnit cu = /* we have an AST */

// JSS can calculate the type of any expression


[Link]();

myExpression resolved type


1+2 int
2.0 * 3 double
"foo".charAt(0) char
“foo”.length() int
new A() [Link].A
Hello, JavaSymbolSolver!

CompilationUnit cu = /* we have an AST */

// JSS can figure out which method has been called


ResolvedMethodDeclaration methodDeclaration =
[Link]();
[Link]();

methodCall Method declaration


[Link](0) [Link](int)
[Link](“a”) [Link](String)
"foo".charAt(0) [Link](int)
“foo”.length() [Link]()
new LinkedList<String>().size() [Link]()
new LinkedList<String>().toString() [Link]()
Hello, JavaSymbolSolver!

CompilationUnit cu = /* we have an AST */

// JSS knows if two types are assignables


type1 = [Link]();
type2 = [Link]().getType();
if ([Link](type2)) { … }

type1 type2 result


int double false

double int true

Collection<Int> List<Int> true


Collection<Double> List<Int> false
Collection<? extends String> List<String> true
Comments attribution
void foo() { // comment1
// comment1 int a =
int a = 1 + 2;
1 + 2; // comment2
}

CompilationUnit cu = [Link](code);

ExpressionStmt expressionStmt =
[Link]([Link]).get() ;

[Link]("Comment on the expression statement: "


+ [Link]().get().getContent());
Comments attribution
void foo() {
// comment1 1 + 2 // comment2
int a =
1 + 2; // comment2
}

VariableDeclarationExpr expr =
(VariableDeclarationExpr)expressionStmt
.getExpression();
VariableDeclarator variableDeclarator =
[Link]().get(0);
Expression init = variableDeclarator
.getInitializer().get();

[Link]("Comment on the initializer: "


+ [Link]().get().getContent());
Can you show me the AST?
Node node = parseBodyDeclaration(
"public Class<? extends String> methodName(String arg) {}");

// If your grandpa needs the AST


[Link](new XmlPrinter(true).output(node));

// Because JavaScript has won


[Link](new JsonPrinter(true).output(node));

// Also hipsters need to see an AST


[Link](new YamlPrinter(true).output(node));

// To generate a diagram with Graphviz


[Link](new DotPrinter(true).output(node));
Can you show me the AST?
root(Type=MethodDeclaration):
body(Type=BlockStmt):
type(Type=ClassOrInterfaceType):
name(Type=SimpleName):
identifier: "Class"
typeArguments:
- typeArgument(Type=WildcardType):
extendedType(Type=ClassOrInterfaceType):
name(Type=SimpleName):
identifier: "String"
name(Type=SimpleName):
identifier: "methodName"
parameters:
- parameter(Type=Parameter):
isVarArgs: "false"
name(Type=SimpleName):
identifier: "arg"
type(Type=ClassOrInterfaceType):
name(Type=SimpleName):
identifier: "String"
Lexical preservation
JavaParser can do pretty printing

String code = "class MyClass{int a;float b;void bar(){}}";


CompilationUnit cu = [Link](code);
[Link]([Link]());

class MyClass {

int a;

float b;

void bar() {
}

}
Lexical preservation
JavaParser can do also do lexical preservation

String code = "class MyClass {int a;float b;void bar(){}}";


ParserConfiguration parserConfiguration =
new ParserConfiguration()
.setLexicalPreservationEnabled(true);
CompilationUnit cu = new JavaParser(parserConfiguration)
.parse(ParseStart.COMPILATION_UNIT,
new StringProvider(code)
).getResult().get();
[Link]([Link]());

class MyClass {int a; void bar(){}}";


Configuring JavaSymbolSolver

JSS needs one thing: that you tell it where to look for classes.

• CombinedTypeSolver to group different type solvers


• AarTypeSolver look into an aar package
• JarTypeSolver look into a jar package
• JavaParserTypeSolver look into a directory of Java files
• MemoryTypeSolver for testing purposes
• ReflectionTypeSolver use reflection (useful to java(x).* classes)
Configuring JavaSymbolSolver

A typical usage:

CombinedTypeSolver typeSolver = new CombinedTypeSolver(


new ReflectionTypeSolver(),
new JavaParserTypeSolver(new File("src/main/java")),
new JavaParserTypeSolver(new File("src/test/java")),
new JarTypeSolver("libs/[Link]"),
new JarTypeSolver("libs/[Link]"));
JavaParser to run queries

Setup: let’s consider the code from Hamcrest

// The directory where there is the code


File hamcrestCoreDir = new File(
"src/main/resources/JavaHamcrest-src/hamcrest-
core/src/main/java");

// Configure the Symbol Solver


CombinedTypeSolver typeSolver = new CombinedTypeSolver(
new ReflectionTypeSolver(),
new JavaParserTypeSolver(hamcrestCoreDir));

// Use our Symbol Solver while parsing


ParserConfiguration parserConfiguration =
new ParserConfiguration()
.setSymbolResolver(new JavaSymbolSolver(typeSolver));
JavaParser to run queries

Setup: let’s consider the code from Hamcrest

// Parse all source files


SourceRoot sourceRoot = new
SourceRoot([Link]());
[Link](parserConfiguration);
List<ParseResult<CompilationUnit>> parseResults =
[Link]("");

// Now get all compilation units


List<CompilationUnit> allCus = [Link]()
.filter(ParseResult::isSuccessful)
.map(r -> [Link]().get())
.collect([Link]());
JavaParser to run queries

Question: How many methods take more than 3 parameters?

long n = getNodes(allCus, [Link])


.stream()
.filter(m -> [Link]().size() > 3)
.count();
[Link]("N of methods with 3+ params: " + n);

Answer: 11
JavaParser to run queries

Question: What are the three top classes with most methods?

getNodes(allCus, [Link])
.stream()
.filter(c -> ![Link]())
.sorted([Link](o ->
-1 * [Link]().size()))
.limit(3)
.forEach(c ->
[Link]([Link]() + ": " +
[Link]().size() + " methods"));

Answer: CoreMatchers: 35 methods


BaseDescription: 13 methods
IsEqual: 9 methods
JavaParser to run queries
Question: What is the class with most ancestors?
ResolvedReferenceTypeDeclaration c = getNodes(allCus,
[Link])
.stream()
.filter(c -> ![Link]())
.map(c -> [Link]()) JSS at work here
.sorted([Link](o ->
-1 * [Link]().size()))
.findFirst().get();
List<String> ancestorNames = [Link]()
.stream()
.map(a -> [Link]())
.collect([Link]());
[Link]([Link]() + ": " +
[Link](", ", ancestorNames));

Answer: [Link]: [Link],


[Link], [Link], [Link],
[Link], [Link]
JavaParser to identify patterns

private static boolean isClassUsingSingleton(


ClassOrInterfaceDeclaration c) {
List<VariableDeclarator> fields = [Link]()
.stream()
.filter(f -> [Link]()
&& [Link]())
.map(FieldDeclaration::getVariables)
.flatMap(Collection::stream)
.filter(v -> isThisClass(c, [Link]().getType()))
.collect([Link]());

}
JavaParser to identify patterns

private static boolean isClassUsingSingleton(


ClassOrInterfaceDeclaration c) {

List<Pair<MethodDeclaration, VariableDeclarator>> pairs =
[Link]()
.stream()
.filter(m -> [Link]()
&& [Link]()
&& isThisClass(c, [Link]().resolve())
&& [Link]().isPresent())
.filter(m -> fieldReturned(m, fields).isPresent())
.map(m -> new Pair<>(m, fieldReturned(m,
fields).get()))
.collect([Link]());
return ![Link]();
}
JavaParser to identify patterns

private static boolean isThisClass(


ClassOrInterfaceDeclaration classOrInterfaceDeclaration,
ResolvedType type) {
return [Link]()
&& [Link]().getQualifiedName()
.equals(classOrInterfaceDeclaration
.resolve().getQualifiedName());
}
JavaParser to identify patterns
private static Optional<VariableDeclarator> fieldReturned(
MethodDeclaration methodDeclaration,
List<VariableDeclarator> fields) {
if ([Link]().get()
.getStatements().size() != 1) {
return [Link]();
}
Statement statement = [Link]()
.get().getStatement(0);
if (![Link]() ||
![Link]()
.getExpression().isPresent()) {
return [Link]();
}

}
JavaParser to identify patterns
private static Optional<VariableDeclarator> fieldReturned(
MethodDeclaration methodDeclaration,
List<VariableDeclarator> fields) {

Expression expression = [Link]()
.getExpression().get();
if (![Link]()) {
return [Link]();
}
Optional<VariableDeclarator> field = [Link]()
.filter(f -> [Link]()
.equals([Link]()
.getNameAsString()))
.findFirst();
return field;
}
JavaParser to identify patterns
We are working on the Matcher library to reduce the complexity, it is in the early stages

This gives you a list of pairs name-type for all the properties in your bean.
JavaParser for automated
refactoring
A new version of a library comes up and a deprecated method named oldMethod is
replaced by newMethod. The new method takes 3 parameters: the first one as
oldMethod but inverted and the third one is a boolean, which we want to be always
true

getNodes(allCus, [Link])
.stream()
.filter(m -> [Link]()
.getQualifiedSignature()
.equals("[Link]([Link],
int)"))
.forEach(m -> [Link](replaceCallsToOldMethod(m)));
JavaParser for automated
refactoring
A new version of a library comes up and a deprecated method named oldMethod is
replaced by newMethod. The new method takes 3 parameters: the first one as
oldMethod but inverted and the third one is a boolean, which we want to be always
true

public MethodCallExpr replaceCallsToOldMethod(


MethodCallExpr methodCall) {
MethodCallExpr newMethodCall = new MethodCallExpr(
[Link]().get(), "newMethod");
[Link]([Link](1));
[Link]([Link](0));
[Link](new BooleanLiteralExpr(true));
return newMethodCall;
}
JavaParser to generate code
CompilationUnit cu = new CompilationUnit();
[Link]("[Link]");
ClassOrInterfaceDeclaration book = [Link]("Book");
[Link]("String", "title");
[Link]("Person", "author");
JavaParser to generate code
[Link]([Link])
.addParameter("String", "title")
.addParameter("Person", "author")
.setBody(new BlockStmt()
.addStatement(new ExpressionStmt(new AssignExpr(
new FieldAccessExpr(
new ThisExpr(), "title"),
new NameExpr("title"),
[Link])))
.addStatement(new ExpressionStmt(new AssignExpr(
new FieldAccessExpr(
new ThisExpr(), "author"),
new NameExpr("author"),
[Link]))));

[Link]([Link]());
JavaParser to generate code
[Link]("getTitle", [Link]).setBody(
new BlockStmt().addStatement(
new ReturnStmt(new NameExpr("title"))));
[Link]("getAuthor", [Link]).setBody(
new BlockStmt().addStatement(
new ReturnStmt(new NameExpr("author"))));

[Link]([Link]());
JavaParser to generate code
package [Link];

public class Book {


String title;
Person author;

public Book(String title, Person author) {


[Link] = title;
[Link] = author;
}

public void getTitle() {


return title;
}

public void getAuthor() {


return author;
}
}
JavaParser: what can we use it for?

What we can do Why could we do it

Generate new Java code Stop writing boilerplate

Because large refactoring are


Modifying existing code
boring and error-prone

So we can answers and data on


which to take decision.
Running queries on code
Also, we can enforce our own
rules
JavaParser: Visited

Book on JavaParser and


JavaSymbolSolver, from the core
committers.

Avalaible for 0+ $

Currently 900+ readers

[Link]

Federico Tomassetti

You might also like