Search code examples
javajavaparser

JavaParser: parsing and generating Java code


I have read the JavaParser manual and started to build my own examples. What I intend to achieve, is to read Java code and insert new lines of code on it. Specifically, I want to initialise a counter before every if and while statement, and inside the body of the statement to increment the counter. My purpose for doing this, is to run the new code for a specified set of runs and observe how many times each branch is executed. I am using JavaParser to parse and add code, because I want to generate and run everything automatically.

For example, we have the following simple piece of code:

if (x>4) { 
   x=4; 
} 
else { 
   x=4; 
} 
while (i<x) {
   i++;
} 

And after the parsing I would like to have something like the following:

int countA=0; 
int countB=0;            
if (x>4) { 
   x=4; 
   countA++;
} 
else { 
   x=4; 
   countB++;
} 
int countC=0;
while (i<x) {
   i++;
   countC++;
} 

My code so far:

public static void main (String[] args) throws Exception {          
              CompilationUnit cu = StaticJavaParser.parse("class X{void x(){" +
              "    if (x>4) {  " +
              "      x=4;  " +
              "    }  " +
              "    else {  " +
              "        x=4;  " +
              "    }  " +
              "    while (i<x) {  " +
              "        i++;  " +
              "    }  " +
              "    }}");
                              
              cu.findAll(Statement.class).forEach(statement -> {             
                  if (statement.isIfStmt()) {
                      //System.out.println(statement.getChildNodes().get(0));
                     // System.out.println(statement.getParentNodeForChildren().toString());
                     // statement.getChildNodes().add(statement.getChildNodes().get(0));
                
                     // System.out.println(statement.toString());
                  }
                  if (statement.isWhileStmt()) {
                     // System.out.println();
                  }
              });
              System.out.println(cu);
            }
}

I have tried some things (the commended out lines) but unsuccessfully. My main problem is that I cannot find a way to inject any line of code at the end of the childNodes of the given statement which supposedly would be the increment of the counter or at the end of the parentNode which would be the initialisation. Is there any way to achieve what I am describing?


Solution

  • The problem in your code is the following:

    if (statement.isIfStmt()) {
    

    The condition clearly state that you are dealing with an if statement and thus you cannot actually add an instruction. What you need to do is to get the n+1 element (where n is the index of the if statement) and then use:

    cu.findAll(Statement.class).get(2).asBlockStmt().addStatement("countA++;")
    

    There are several ways to define the expression to be added, here I'm using a plain string for clarity but check the working example for actual syntax.

    For the while you can use the same technique but it's going to be more complicated. For simplicity the declarations of your counters will be added at the beginning of the method using the following approach:

    ((BlockStmt)cu.findAll(Statement.class).get(6).getParentNode().get()).addStatement(0, new StringLiteralExpr("int b = 1;"));
    

    Exaplanation

    • The sixth statement of the list of all statements is the while loop
    • The parent node of this statement is the main block of the method and is where you want to add your instructions
    • This object needs to be class casted to BlockStmt to be able to use the addStatement method
    • StringLiteralExpr is a subclass of Expression and it is here used to create an instance from a string. There are many other different implementations you can explore.

    I reproduced your code making a working JUnit 5 and this is the result I got

    public class ParserTest {

    String counterName = "count";
    Integer counterIndex = 1;
    
    @Test
    public void main() {
    
        CompilationUnit cu = StaticJavaParser.parse("class X{void x(){" + "    if (x>4) {  " + "      x=4;  "
                + "    }  " + "    else {  " + "        x=4;  " + "    }  " + "    while (i<x) {  " + "        i++;  "
                + "    }  " + "    }}");
    
        cu.findAll(Statement.class).forEach(statement -> {
            if (statement.isIfStmt()) {
    
                // Add counter declaration in main block for the if
                String counterNameAndIndexIf = counterName + counterIndex++;
                // Create expression using StaticJavaParser
                Expression ifCounterExpression = StaticJavaParser
                        .parseVariableDeclarationExpr(" int " + counterNameAndIndexIf + " = 0");
                ((BlockStmt) statement.getParentNode().get()).addStatement(0, ifCounterExpression);
    
                // Add counter declaration in main block for the else
                String counterNameAndIndexElse = counterName + counterIndex++;
                // Create expression using StaticJavaParser
                Expression elseCounterExpression = StaticJavaParser
                        .parseVariableDeclarationExpr("int " + counterNameAndIndexElse + " = 0");
                ((BlockStmt) statement.getParentNode().get()).addStatement(0, elseCounterExpression);
    
                // Add if increment
                Expression ifIncrementExpression = StaticJavaParser.parseExpression(counterNameAndIndexIf + "++");
                ((BlockStmt) statement.getChildNodes().get(1)).addStatement(ifIncrementExpression);
    
                // Add else increment
                Expression elseIncrementExpression = StaticJavaParser.parseExpression(counterNameAndIndexElse + "++");
                ((BlockStmt) statement.getChildNodes().get(2)).addStatement(elseIncrementExpression);
            }
            if (statement.isWhileStmt()) {
                String counterNameAndIndexWhile = counterName + counterIndex++;
                Expression whileCounterExpression = StaticJavaParser
                        .parseVariableDeclarationExpr(" int " + counterNameAndIndexWhile + " = 0");
                ((BlockStmt) statement.getParentNode().get()).addStatement(0, whileCounterExpression);
    
                // Add while increment
                Expression whileIncrementExpression = StaticJavaParser.parseExpression(counterNameAndIndexWhile + "++");
                ((BlockStmt) statement.getChildNodes().get(1)).addStatement(whileIncrementExpression);
    
            }
        });
        System.out.println(cu);
    }
    

    And the result is

    class X {
    
    void x() {
        int count3 = 0;
        int count2 = 0;
        int count1 = 0;
        if (x > 4) {
            x = 4;
            count1++;
        } else {
            x = 4;
            count2++;
        }
        while (i < x) {
            i++;
            count3++;
        }
    }
    

    Hope this helps. Let me know if you need clarifications on how to build any of these objects. There are for sure smarter ways and code can be obviously refactored but I wanted to have it laid down as plainly as possible for you.

    Cheers

    EDIT: to support statement nesting and declaring the all the counter variables in the main block of the method the following can be used

    statement.findRootNode().getChildNodes().get(0).getChildNodes().get(1).getChildNodes().get(2)
    

    This approach will start top down from the root and will always point to the main block. Use it to add your counter variables.