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

254 Views Asked by At

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

  • not variables access into method, only access list for fields of class
  • all types of access (assigns, ++, --,...)
  • It is better to print separately (read access and write access)
  • only fields access list for fields of desired class (not other classes fields access)

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
3

There are 3 best solutions below

0
devatherock On BEST ANSWER

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.

0
Olivier On

Here is my solution. It works as follows:

  • Parse the source to get the AST tree and search for class declarations.

  • For every class, search for method declarations.

  • For every method, search for FieldAccessExpr and NameExpr nodes.

  • For every FieldAccessExpr node: test the object (called scope) on which the field is accessed. If the scope is this, collect the field name.

  • For every NameExpr node: call the resolve() method to find the declaration associated with it. In case of a field declaration, collect the field name.

As a bonus, I have included a dump() method which shows how to easily print an AST tree.

Code:

import java.io.*;
import java.util.TreeSet;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.printer.YamlPrinter;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver;
import static com.github.javaparser.ast.Node.TreeTraversal.DIRECT_CHILDREN;

public class FieldAccessList
{
    public static void parse(File file) throws FileNotFoundException
    {
        JavaSymbolSolver resolver = new JavaSymbolSolver(new MemoryTypeSolver());
        StaticJavaParser.getParserConfiguration().setSymbolResolver(resolver);
        CompilationUnit cu = StaticJavaParser.parse(file);
//      dump(cu);

        for(ClassOrInterfaceDeclaration cd : cu.findAll(ClassOrInterfaceDeclaration.class, DIRECT_CHILDREN))
        {
            System.out.println("Class: " + cd.getNameAsString());
            for(MethodDeclaration md : cd.getMethods())
            {
                TreeSet<String> fields = new TreeSet<String>();
                md.walk(node -> {
                    if(node instanceof FieldAccessExpr)
                        checkNode((FieldAccessExpr)node, fields);
                    else if(node instanceof NameExpr)
                        checkNode((NameExpr)node, fields);
                });
                System.out.println("  Method: " + md.getNameAsString());
                for(String f : fields)
                    System.out.println("    Field access: " + f);
            }
        }
    }

    private static void checkNode(FieldAccessExpr node, TreeSet<String> fields)
    {
        if(node.getScope().isThisExpr())
            fields.add(node.getNameAsString());
    }

    private static void checkNode(NameExpr node, TreeSet<String> fields)
    {
        try
        {
            ResolvedValueDeclaration decl = node.resolve();
            if(decl.isField())
                fields.add(node.getNameAsString());
        }
        catch(UnsolvedSymbolException e)
        {
        }
    }

    public static void dump(Node node)
    {
        YamlPrinter printer = new YamlPrinter(true);
        printer.print(node);
    }

    public static void main(String[] args) throws FileNotFoundException
    {
        parse(new File("Example.java"));
    }
}

Test file:

public class Example
{
    private int field1;
    private String field2;

    // Explicit access to a field
    public int method1()
    {
        return this.field1;
    }

    // Implicit access to a field
    public int method2()
    {
        return field1;
    }

    // Writing to a field
    public void method3()
    {
        field2 = "";
    }

    // Writing to a variable which has the same name as a field
    public void method4()
    {
        String field2;
        field2 = "";
    }

    // Incrementing a field
    public int method5()
    {
        return field1++;
    }

    // Method call on System.out
    public void method6()
    {
        System.out.println(field2);
    }

    // Expression involving both fields
    public String method7()
    {
        return field2 + " " + field1;
    }
}

Output:

Class: Example
  Method: method1
    Field access: field1
  Method: method2
    Field access: field1
  Method: method3
    Field access: field2
  Method: method4
  Method: method5
    Field access: field1
  Method: method6
    Field access: field2
  Method: method7
    Field access: field1
    Field access: field2
10
Anish B. On

Finally, I found the easier way to solve this.

Please update your code to 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.AssignExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import java.io.File;
import java.io.IOException;
import java.util.List;

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());

                List<AssignExpr> assignmentStatements = methodDeclaration.findAll(AssignExpr.class);

                assignmentStatements.forEach(assignExpr -> {
                    assignExpr.findAll(NameExpr.class).forEach(nameExpr -> System.out.println("    Field Access: " + nameExpr));
                });

                List<MethodCallExpr> methodCallStatements = methodDeclaration.findAll(MethodCallExpr.class);

                methodCallStatements.forEach(methodCallExpr -> methodCallExpr.getArguments().forEach(arg -> {
                    System.out.println("    Field Access: " + arg);
                }));
            });
        });
    }
}

If you run this code, you will get the output that you were expecting.

enter image description here

Few points to be noted:

  • First of all, there are two kinds of statements - one is Assignment statement (AssignExpr) and another one is Method Call statement (MethodCallExpr).

  • The parsing of Assignment and Method Call statements should be done in different approaches.

  • For Method Call statements, you can just call getArguments() method to get all the field names used in the method call.

  • Mostly, field names are of type NameExpr instance.

See if this helps you.


Update: As per @Olivier request, I have updated the code to handle more scenarios:

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.body.VariableDeclarator;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.ReturnStmt;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class FieldAccessList {

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

        File sourceFile = new File("/Users/anisb/Downloads/testing/src/main/java/org/example/Example.java");
        CompilationUnit cu = StaticJavaParser.parse(sourceFile);

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

            List<MethodDeclaration> declarations = classDeclaration.findAll(MethodDeclaration.class);

            declarations.forEach(methodDeclaration -> {
                System.out.println("  Method: " + methodDeclaration.getNameAsString());

                List<VariableDeclarationExpr> variableDeclarations = methodDeclaration.findAll(VariableDeclarationExpr.class);

                variableDeclarations.forEach(variableDeclaration -> {
                    variableDeclaration.findAll(VariableDeclarator.class).forEach(fieldAccessExpr -> System.out.println("    Variable Declaration: " + fieldAccessExpr.getNameAsString()));
                });

                List<AssignExpr> assignmentStatements = methodDeclaration.findAll(AssignExpr.class);

                assignmentStatements.forEach(assignExpr -> {
                    assignExpr.findAll(FieldAccessExpr.class).forEach(fieldAccessExpr -> System.out.println("    Field Access: " + fieldAccessExpr.getNameAsString()));
                    assignExpr.findAll(NameExpr.class).forEach(nameExpr -> System.out.println("    Variable/Field (via name) Access: " + nameExpr));
                });

                List<MethodCallExpr> methodCallStatements = methodDeclaration.findAll(MethodCallExpr.class);

                methodCallStatements.forEach(methodCallExpr -> methodCallExpr.getArguments().forEach(arg -> {
                    arg.findAll(FieldAccessExpr.class).forEach(fieldAccessExpr -> System.out.println("    Field Access: " + fieldAccessExpr.getNameAsString()));
                    arg.findAll(NameExpr.class).forEach(nameExpr -> System.out.println("    Variable/Field (via name) Access: " + nameExpr));
                }));

                List<ReturnStmt> returnStatements = methodDeclaration.findAll(ReturnStmt.class);

                returnStatements.forEach(returnStatement -> {
                    returnStatement.findAll(FieldAccessExpr.class).forEach(fieldAccessExpr -> System.out.println("    Field Access: " + fieldAccessExpr.getNameAsString()));
                    returnStatement.findAll(NameExpr.class).forEach(nameExpr -> System.out.println("   Variable/Field (via name) Access: " + nameExpr));
                });

                List<UnaryExpr> unaryStatements = methodDeclaration.findAll(UnaryExpr.class);
                unaryStatements.forEach(unaryStatement -> {
                    unaryStatement.findAll(FieldAccessExpr.class).forEach(fieldAccessExpr -> System.out.println("    Field Access: " + fieldAccessExpr.getNameAsString()));
                    unaryStatement.findAll(NameExpr.class).forEach(nameExpr -> System.out.println("   Variable/Field (via name): " + nameExpr));
                });
            });
        });
    }
}

I'm using the same Example class mentioned in the @Olivier answer to demonstrate the scenarios.

public class Example {

    private int field1;
    private String field2;

    public int method1() {
        return this.field1;
    }

    public int method2() {
        return field1;
    }

    public void method3() {
        field2 = "";
    }

    public void method4() {
        String field2;
        field2 = "";
    }

    public int method5() {
        return field1++;
    }

    public void method6() {
        System.out.println(field2);
    }

    public String method7() {
        return field2 + " " + field1;
    }

    public void method8() {
        field1++;
    }

    public void method9() {
        this.field1 = 0;
    }

    public void method10() {
        String s = field2;
    }

    public void method11() {
        String s;
        s = "";
    }

    public void method12() {
        int field4 = 1;
        int field5 = 2;
        int sum = field4 + field5;
    }

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

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

}

Output:

Class: Example
  Method: method1
    Field Access: field1
  Method: method2
   Variable/Field (via name) Access: field1
  Method: method3
    Variable/Field (via name) Access: field2
  Method: method4
    Variable Declaration: field2
    Variable/Field (via name) Access: field2
  Method: method5
   Variable/Field (via name) Access: field1
   Variable/Field (via name): field1
  Method: method6
    Variable/Field (via name) Access: field2
  Method: method7
   Variable/Field (via name) Access: field2
   Variable/Field (via name) Access: field1
  Method: method8
   Variable/Field (via name): field1
  Method: method9
    Field Access: field1
  Method: method10
    Variable Declaration: s
  Method: method11
    Variable Declaration: s
    Variable/Field (via name) Access: s
  Method: method12
    Variable Declaration: field4
    Variable Declaration: field5
    Variable Declaration: sum
  Method: method13
    Variable/Field (via name) Access: field1
    Variable/Field (via name) Access: field2
  Method: method14
    Variable/Field (via name) Access: field2