javajavaparser

How to print all types of read and write access list to class fields for each methods of class in Java with JavaParser library


I want to print all field access list for each method of a class in Java with JavaParser Library (3.25.8).

I try this:

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import java.io.File;
import java.io.IOException;

public class FieldAccessList {

    public static void main(String[] args) throws IOException {

        File sourceFile = new File("Example.java");
        CompilationUnit cu = StaticJavaParser.parse(sourceFile);

        cu.findAll(ClassOrInterfaceDeclaration.class).forEach(classDeclaration -> {
            System.out.println("Class: " + classDeclaration.getNameAsString());

            classDeclaration.findAll(MethodDeclaration.class).forEach(methodDeclaration -> {
                System.out.println("  Method: " + methodDeclaration.getNameAsString());

                methodDeclaration.findAll(FieldAccessExpr.class).forEach(fieldAccessExpr -> {
                    System.out.println("    Field Access: " + fieldAccessExpr.getNameAsString());
                });
            });
        });
    }
}

and my pom.xml is:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
         
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>Sahand</groupId>
    <artifactId>Importance</artifactId>
    <version>2.0</version>
    <name>Sahand Project Extension</name>
    
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    
    <dependencies>
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-core</artifactId>
            <version>3.25.8</version>
        </dependency>

        
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-symbol-solver-core</artifactId>
            <version>3.25.8</version>
        </dependency>
    
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>

for Example.java:

public class Example {

    private int field1;
    private String field2;

    public void method1() {
        field1 = 10;
        System.out.println(field2);
    }

    public void method2() {
        field2 = "Hello";
    }
}

The output I expected should be:

Class: Example
  Method: method1
    Field Access: field1
    Field Access: field2
  Method: method2
    Field Access: field2

But the output is:

Class: Example
  Method: method1
    Field Access: out
  Method: method2

Solution

  • The javadoc of FieldAccessExpr says it is meant for detecting accesses of the type person.name, which is probably why it detected System.out. Through some trial and error, I figured out that the expression field1 = 10; is of type AssignExpr and that System.out.println(field2); is of type MethodCallExpr. A ++ or -- expression is of type UnaryExpr. Also to filter out expressions that only use local variables, I collected all of the class level fields at the start and included only expressions that involved any of those fields. Combining all of that together, for the Example class, I'm able to get the expected output using below code:

    import com.github.javaparser.StaticJavaParser;
    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.body.MethodDeclaration;
    import com.github.javaparser.ast.expr.AssignExpr;
    import com.github.javaparser.ast.expr.Expression;
    import com.github.javaparser.ast.expr.MethodCallExpr;
    import com.github.javaparser.ast.expr.UnaryExpr;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class TestUtil {
    
        public static void listFieldAccess() throws FileNotFoundException {
            File sourceFile = new File("Example.java");
            CompilationUnit cu = StaticJavaParser.parse(sourceFile);
    
            cu.findAll(ClassOrInterfaceDeclaration.class).forEach(classDeclaration -> {
                System.out.println("Class: " + classDeclaration.getNameAsString());
                List<String> fields = new ArrayList<>();
    
                // Find all field names
                classDeclaration.findAll(FieldDeclaration.class).forEach(fieldDeclaration -> {
                    fieldDeclaration.getVariables().forEach(variable -> {
                        fields.add(variable.getNameAsString());
                    });
                });
    
                classDeclaration.findAll(MethodDeclaration.class).forEach(methodDeclaration -> {
                    System.out.println("  Method: " + methodDeclaration.getNameAsString());
    
                    methodDeclaration.findAll(Expression.class).forEach(expression -> {
                        // Process only specific types of expressions
                        if (expression instanceof MethodCallExpr || expression instanceof AssignExpr ||
                                expression instanceof UnaryExpr) {
                            // Check if any of the expression fields match the class level fields
                            List<String> matchedFields = fields.stream().filter(field -> {
                                return expression.getChildNodes().stream().anyMatch((node) -> node.toString().contains(field));
                            }).collect(Collectors.toList());
                            System.out.println("Field access: " + matchedFields);
                        }
                    });
                });
            });
        }
    }
    

    Couldn't figure out exactly how to differentiate between a read and write access as something like a method call could read or write internally. Also you might still need to include more expression types and add some filtering, to include only fields within the desired class.