The JavaParser library provides an abstract syntax tree for Java code. The AST structure allows you to use Java code in a simple programmatic way.

When using JavaParser, we usually want to perform a series of operations at a time. Usually, we want to operate on the entire project, so given a directory, we will explore all Java files. This class should help to accomplish the following operations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.File;
public class DirExplorer {
    public interface FileHandler {
        void handle(int level, String path, File file);
    }
    public interface Filter {
        boolean interested(int level, String path, File file);
    }
    private FileHandler fileHandler;
    private Filter filter;
    public DirExplorer(Filter filter, FileHandler fileHandler) {
        this.filter = filter;
        this.fileHandler = fileHandler;
    }
    public void explore(File root) {
        explore(0, "", root);
    }
    private void explore(int level, String path, File file) {
        if (file.isDirectory()) {
            for (File child : file.listFiles()) {
                explore(level + 1, path + "/" + child.getName(), child);
            }
        } else {
            if (filter.interested(level, path, file)) {
                fileHandler.handle(level, path, file);
            }
        }
    }
}

For each Java file, we would like to first construct an abstract syntax tree AST for each Java file and then navigate through it. There are two main strategies to do this.

  • using a visitor: this is the right strategy when you want to operate on a specific type of AST node
  • Use recursive iterators: this allows to handle all types of nodes

Visitors can write extension classes included in JavaParser, and this is a simple node iterator:.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import com.github.javaparser.ast.Node;
public class NodeIterator {
    public interface NodeHandler {
        boolean handle(Node node);
    }
    private NodeHandler nodeHandler;
    public NodeIterator(NodeHandler nodeHandler) {
        this.nodeHandler = nodeHandler;
    }
    public void explore(Node node) {
        if (nodeHandler.handle(node)) {
            for (Node child : node.getChildrenNodes()) {
                explore(child);
            }
        }
    }
}

Now let’s see how we can use this code to solve some problems in stack overflow.

How to extract the names of all classes from a Java class in a common string?

This solution can be solved by looking for the ClassOrInterfaceDeclaration node. If we need a specific type of node, we can use a visitor. Note that VoidVisitorAdapter allows passing arbitrary parameters. In this case, we don’t need to do that, so we specify the Object type and we just ignore it in the access method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseException;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.google.common.base.Strings;
import me.tomassetti.support.DirExplorer;
import java.io.File;
import java.io.IOException;
public class ListClassesExample {
    public static void listClasses(File projectDir) {
        new DirExplorer((level, path, file) -> path.endsWith(".java"), (level, path, file) -> {
            System.out.println(path);
            System.out.println(Strings.repeat("=", path.length()));
            try {
                new VoidVisitorAdapter<Object>() {
                    @Override
                    public void visit(ClassOrInterfaceDeclaration n, Object arg) {
                        super.visit(n, arg);
                        System.out.println(" * " + n.getName());
                    }
                }.visit(JavaParser.parse(file), null);
                System.out.println(); // empty line
            } catch (ParseException | IOException e) {
                new RuntimeException(e);
            }
        }).explore(projectDir);
    }
    public static void main(String[] args) {
        File projectDir = new File("source_to_parse/junit-master");
        listClasses(projectDir);
    }
}

We ran the example on JUnit’s source code and got the following output.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/src/test/java/org/junit/internal/MethodSorterTest.java
=======================================================
 * DummySortWithoutAnnotation
 * Super
 * Sub
 * DummySortWithDefault
 * DummySortJvm
 * DummySortWithNameAsc
 * MethodSorterTest
/src/test/java/org/junit/internal/matchers/StacktracePrintingMatcherTest.java
=============================================================================
 * StacktracePrintingMatcherTest
/src/test/java/org/junit/internal/matchers/ThrowableCauseMatcherTest.java
=========================================================================
 * ThrowableCauseMatcherTest
... 
... many other lines follow

Is there any Java code parser that returns the line number of the code? In this case, there are several classes that extend the Statement base class, so I can use the visitor, but I need to write the same code in multiple access methods, each for a subclass of Statement. In addition, I only want to get the top-level statements, not the statements within them. For example, the For statement can contain several other statements. Using our custom NodeIterator, we can easily implement this logic.