Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I access the contents of my code? #2149

Closed
is742 opened this issue Apr 2, 2019 · 26 comments
Closed

How can I access the contents of my code? #2149

is742 opened this issue Apr 2, 2019 · 26 comments
Labels
Question (JP usage) How to use JP to inspect/modify the AST - please close your question once answered!

Comments

@is742
Copy link

is742 commented Apr 2, 2019

Hi, I am new to Java Parser and I'm trying to understand how to use some of its features - if what I am thinking can be achieved.

I have this piece of code, mainly for experimenting with Java Parser:

                 int x = 4;
		if (x>3) {
			for (int i=0; i<4; i++)
				//TO-DO
		}
		else {
			if (x>1) {
				//TO-DO
			}
			else {
				//TO-DO
			}
		}

What I would like to achieve is to call JP, and based on the order of the constructs to perform some operations. So far I have tried to do this for the if statements with the public void visit(IfStmt n, Void arg) method, but this method just identifies all the ifs located in my code, without preserving the order (the second if is inside the first and not below).

Is there a way to identify all constructs in my code and preserve their order? (I am not looking to print the AST).

@matozoid
Copy link
Contributor

matozoid commented Apr 2, 2019

Hi! Have you read the book?

@matozoid matozoid added the Question (JP usage) How to use JP to inspect/modify the AST - please close your question once answered! label Apr 2, 2019
@is742
Copy link
Author

is742 commented Apr 2, 2019

Yes - I am using the book and the JavaDocs as a tutorial/guide. Would you recommend to look into a specific chapter? I have not found what I am looking for so far.

@matozoid
Copy link
Contributor

matozoid commented Apr 2, 2019

Okay. The order of visiting is set in the visitor you are inheriting from. I guess your inherited visit method calls super.visit first? That's when it travels deeper into the tree. Try putting the super.visit last. Don't be afraid to look into the visitor's source code either :-)

If I'm mistaken, it would be helpful to see the code you are writing.

@is742
Copy link
Author

is742 commented Apr 2, 2019

private static class MethodVisitor extends VoidVisitorAdapter<Void> {
        @Override
        public void visit(IfStmt n, Void arg) {
        	countStates++;
            super.visit(n, arg);
        }
        @Override
        public void visit(ForStmt f, Void arg) {
        	countStates++;
            super.visit(f, arg);
        }
    }

I'm using the code above to count how many if-statements and loops appear in the code. I tried to use Statement instead of IfStmt to capture all the possible statements (e.g. loops) at once, but with no success.

@matozoid
Copy link
Contributor

matozoid commented Apr 2, 2019

Okay, you asked help with if statements being visited inside out. Is that problem solved? You are counting ifs and fors now. How is that not working?

@is742
Copy link
Author

is742 commented Apr 2, 2019

The first part of the problem is solved. The second, which I added a piece of my code, works but I was wondering if there is a more optimal way to count all the statements rather than having to create a case for each one of them. I read in the JavaDocs that instead of using e.g. IfStmt or ForStmt, we can use Statement which refers to all of them.

        @Override
        public void visit(Statement f, Void arg) {
        	countStates++;
            super.visit(f, arg);
        }

The above which was my approach to this is wrong though.

@matozoid
Copy link
Contributor

matozoid commented Apr 3, 2019

Ah, like that! The visitor only has methods for the "leaves" of the AST, so no abstract classes like Statement. What you can try is one of the find methods on Node (try your compilation unit.)

@is742
Copy link
Author

is742 commented Apr 9, 2019

Thanks for your replies they really helped me a lot. I'm following your proposal to use the find method and I came up with a small example that records the transitions throughout a small program:

The small Java program:

                int x = 5;
		if (x>3) {
			x=4;
		}
		else {
			if (x>1) {
				x=4;
			}
			else {
				x=4;
			}
		}
		if (x==4) {
			x=3;
		}

My JP code:

cu.findAll(Statement.class).forEach(statement -> {
        	if (statement.isExpressionStmt()) {
        		System.out.println("s="+countStates+"-> s="+(countStates+1));
        		countStates++;
        	}
        	else if (statement.isIfStmt()) {            	
            		System.out.println("s="+countStates+"-> s="+(countStates+1)+" or s="+(countStates+2));
            		countStates++;            	
        	}
        });
}

And this is my output:

s=0-> s=1
s=1-> s=2 or s=3
s=2-> s=3
s=3-> s=4 or s=5
s=4-> s=5
s=5-> s=6
s=6-> s=7 or s=8
s=7-> s=8

The code is correct and working but I cannot overcome the following issue: E.g. when we are inside the first if (s=2) we are skipping the else and go to s=6 instead of s=3 which my output prints.

s=0-> s=1
s=1-> s=2 or s=3
s=2-> **s=6**
s=3-> s=4 or s=5
s=4-> **s=6**
s=5-> **s=6**
s=6-> s=7 or s=8
s=7-> s=8

Is there a way to identify that we reached the end of the nested ifs and the next state is out of the block and not on the else branch? Please let me know if something is unclear. Thanks again.

@matozoid
Copy link
Contributor

matozoid commented Apr 9, 2019

Okay, I'm guessing at exactly what it is you're trying to achieve, so let's ask that question: what exactly is the statistic you're trying to calculate from your latest example?

@is742
Copy link
Author

is742 commented Apr 9, 2019

I want to classify all the possible states of the program and how they connect to each other, where a state can be any Statement, in this specific case IfStmt and ExpressionStmt.

E.g. from the code:

-first line-
x=5 is state 0 (s=0) which leads to if (x>4) state 1 (s=1)

-second line-
if (x>4) is state 1 (s=1) which can lead to either x=4 which is state 2 (s=2) or to the else branch state 3 (s=3).

-third line-
x=4 is state 2 (s=2). Since we are inside the if branch, means that the else will not be executed and will directly lead us to if (x==4) which is state 6 (s=6) and not state 3 as my code prints.

etc.

My code has managed to do so up to a point, but of course it cannot identify that after we reach state 2 the code will jump to state 6 and won't continue to state 3 which is the else branch. So, I was thinking there might be a way to specify that if we reach the end of the if branch we skip up to the number of the next if. Then maybe with the help of a map we can keep track of the state each if occurs and successfully map it.

@matozoid
Copy link
Contributor

matozoid commented Apr 9, 2019

If I understand correctly, then you need more control. For if-statements, it looks like you first want to find the top level ones. Then, for each of those, you want to find any second-level ifs, and so on. The important thing is to know the relationship of the ifs - is it a sequence or a containtment?

In this case I personally would create a visitor for if statements that does not call super, since it won't automatically continue visiting inside the if statement. But in the visit method you can ask for the same visitor (or another) to visit the true or the false part of the if (getThenStmt().accept(visitor, null);). A single call of the visitor will find ifs that are in sequence, and the nested call will find the nested ifs, including the relationship (since you know if you started that visitor on then or else.)

If this is all too much, try going fully manual: write a recursive method that takes a node, iterates through Node.getChildren() and inspects each of those. Now you have full control.

@is742
Copy link
Author

is742 commented Apr 12, 2019

I've been trying to implement the first approach the past few days but unsuccessfully. This is the closest I got but it prints nothing as output:

VoidVisitor<?> visitor = new IfStmtVisitor();    	
    	cu.findAll(Statement.class).forEach(statement -> {  
                   if (statement.isIfStmt()) {
        	          IfStmt ifr = (IfStmt) statement;
                           ifr.getThenStmt().accept(visitor, null);
                  }
    		
    	});
}
private static class IfStmtVisitor extends VoidVisitorAdapter<Void> {
    	@Override
    	public void visit(IfStmt ifstmt, Void arg) { 
    		super.visit(ifstmt, arg);
    		System.out.println(ifstmt);
    	}
    }

I was hoping this would print the contents of the then statement so I can have something to start working on but it does not print anything. Also, I'm not sure how it is going to find the ifs that are in sequence which is my main problem: My code gets all ifs as they were at the same level.

When I remove super as you said I get an exception and code terminates.

@matozoid
Copy link
Contributor

If I find some free time I can write some code.

@matozoid
Copy link
Contributor

matozoid commented Apr 16, 2019

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.IfStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.visitor.VoidVisitorWithDefaults;

import java.util.ArrayList;
import java.util.List;

public class Bla {
    public static void main(String[] args) {
        CompilationUnit cu = StaticJavaParser.parse("class X{void x(){" +
                "    int x = 1;" +
                "    if (x>4) {  " +
                "      x=21;  " +
                "    }  " +
                "    else {  " +
                "      if (x>1) {  " +
                "        x=211;  " +
                "      }  " +
                "      else {  " +
                "        x=212;  " +
                "      }  " +
                "    }  " +
                "    if (x==4) {  " +
                "      x=3;  " +
                "    }}}");


        State start = new State(null);
        State end = new State(null);

        findStatesInBlock(cu.findFirst(BlockStmt.class).get(), start, end);

    }

    static class State {
        final Statement statement;
        // The states that follow this state:
        final List<State> nextStates = new ArrayList<>();

        State(Statement statement) {
            this.statement = statement;
        }
    }

    /**
     * Make a state graph of the statements  in block. beforeState will get a next-state to the first statement in the block.
     * afterState will be the next-state of the last statement(s) in the block.
     */
    private static void findStatesInBlock(BlockStmt block, State beforeState, State afterState) {
        List<State> states = new ArrayList<>();
        // Create the sequence in this block
        for (Statement statement : block.getStatements()) {
            states.add(new State(statement));
        }

        beforeState.nextStates.add(states.get(0));

        // Attach to next states
        for (int i = 0; i < states.size(); i++) {
            State currentState = states.get(i);
            
            State nextState;
            if (i == states.size() - 1) {
                nextState = afterState;
            } else {
                nextState = states.get(i + 1);
            }

            currentState.statement.accept(new VoidVisitorWithDefaults<Void>() {
                @Override
                public void visit(ExpressionStmt n, Void arg) {
                    currentState.nextStates.add(nextState);
                }

                @Override
                public void visit(IfStmt n, Void arg) {
                    findStatesInBlock(n.getThenStmt().asBlockStmt(), currentState, nextState);
                    n.getElseStmt().ifPresent(elseStmt ->
                            findStatesInBlock(elseStmt.asBlockStmt(), currentState, nextState));
                }
            }, null);
        }
    }
}

After findStatesInBlock has executed, you can find the state graph in root. This implementation is of course far from finished, but I think the main algorithm works.

@is742
Copy link
Author

is742 commented Apr 17, 2019

Thanks for putting the effort to make this example, I appreciate it. With "you can find the state graph in root" you mean for visualisation purposes? I was thinking to create a printMethod to generate something similar with my output in the previous comments.

@matozoid
Copy link
Contributor

You are making a graph of states. The numbers you use are the States in this program. Every State has a list of States that can follow it. If you start it in a debugger and stop it right before the end, you can inspect root and see what the next nodes are, and their next nodes, and their next nodes. It corresponds to the relations you describe.

If you want to print it, you start at root, print some representation, then print an arrow and the states in the nextStates-list. Then you can do the same for those states, and so on.

@is742
Copy link
Author

is742 commented Apr 21, 2019

Indeed this prints exactly what I had in mind with the only addition of a map to count and assign a number in each state. Thus, by calling the method a second time, the map will help with the printing of the correct number of nextStates.

What I'm trying now is to add support for loops, e.g. for WhileStmt. For this purpose I made the following additions to the code:

Code to be parsed (changed the last IfStmt to a WhileStmt):

      "    while (x<=4) {  " +
      "      x++;  " +
      "    }}}");

Added WhileStmt support in the main code:

      @Override
       public void visit(WhileStmt n, Void arg) {
                 currentState.nextStates.add(nextState);
       }

What I noticed is that while the loop is recognised as a statement, nothing gets printed from the contents of the WhileStmt. I think this makes sense, because in theory we do not know how many times the loop is going to be initiated.

Is there a way to print the contents of the loop (meaning the different statements that are inside, in this case x++;) just once? Then perhaps I can point that the last statement inside the loop leads back to the beginning of the loop.

@matozoid
Copy link
Contributor

Okay, I have to draw a line here: my support is to maintain and explain JavaParser. Developing solutions for clients is what I do at work, and that's not for free ;-)

That said, I think you want to have the following things:
(the statement before the while) -> (the statement after the while) // when the while condition is false right away
(the statement before the while) -> (the first statement inside the while) // when the while condition is true
(the last statement in the while) -> (the statement after the while) // when the loop is finished
(the last statement in the while) -> (the first statement inside the while) // when the loop is not finished

Or maybe less, depending on what your ultimate goal is.

@is742
Copy link
Author

is742 commented Apr 21, 2019

Apologies for asking too many questions. You helped me understand some of the core functionalities of JavaParser. Thanks again!

@is742 is742 closed this as completed Apr 21, 2019
@matozoid
Copy link
Contributor

No problem, feel free to ask more :-)

@is742
Copy link
Author

is742 commented Apr 22, 2019

This time I have a more concrete example of a problem rather than a question.

I added else-if statements to the parsing code and I get the following error (if I change from else-if to if it works fine):

Exception in thread "main" java.lang.IllegalStateException: if (x < 4) {
    if (x > 1) {
        x = 211;
    } else {
        x = 212;
    }
} is not an BlockStmt

I'm not sure why this occurs when the ifs works fine, but I tried to solve it in the following way by checking whether an else-if appears:

                @Override
               public void visit(IfStmt n, Void arg) {
                   findStatesInBlock(n.getThenStmt().asBlockStmt(), currentState, nextState);
                   
//new addition---------------------------------------------
                   if (n.getElseStmt().isPresent()) {
                       Statement elseStmt = n.getElseStmt().get();
                       if (elseStmt instanceof IfStmt) {
               			//findStatesInBlock(elseStmt.asBlockStmt(), currentState, nextState);                                                                               	 
                                findStatesInBlock(n.getThenStmt().asBlockStmt(), currentState, nextState);         
                        }
                   }
//end of new addition---------------------------------------
                   else {
                   	n.getElseStmt().ifPresent(elseStmt ->
                   			findStatesInBlock(elseStmt.asBlockStmt(), currentState, nextState));
                   }
               }

The commended out line recreates the error, while the other skips the corresponding else-if and does not print it. Should I approach this in a different way?

@matozoid
Copy link
Contributor

Some hints:

  • .asBlockStmt() is really a cast pretending to be an innocent method. Looks like it was called on something of the wrong type.
  • to find out what types are in the AST, read this article: https://javaparser.org/inspecting-an-ast/

@is742
Copy link
Author

is742 commented Apr 22, 2019

I believe I found what has caused this by inspecting the AST as you suggested, but still I'm not sure why this happens.

The else if statement is assigned to this type elseStmt(Type=IfStmt) while in later cases the then and else statements are of type BlockStmt.

I do not think there is an error in the parsing code. In general, the else if statements are assigned to (Type=IfStmt) even if they contain a block of code?

@matozoid
Copy link
Contributor

By design the else is part of the IfStmt node (if that's what you're asking.) Check out the Javadoc: https://static.javadoc.io/com.github.javaparser/javaparser-core/3.13.6/com/github/javaparser/ast/stmt/IfStmt.html

@is742
Copy link
Author

is742 commented Apr 22, 2019

Sorry, I will try to make it more understandable.

This is the AST generated by my code:

                - statement(Type=IfStmt): 
                                condition(Type=BinaryExpr): 
                                    operator: "GREATER"
                                    left(Type=NameExpr): 
                                        name(Type=SimpleName): 
                                            identifier: "x"
                                    right(Type=IntegerLiteralExpr): 
                                        value: "4"
                                elseStmt(Type=IfStmt): 
                                    condition(Type=BinaryExpr): 
                                        operator: "LESS"
                                        left(Type=NameExpr): 
                                            name(Type=SimpleName): 
                                                identifier: "x"
                                        right(Type=IntegerLiteralExpr): 
                                            value: "4"
                                    thenStmt(Type=BlockStmt): 
                                        statements: 
                                            - statement(Type=IfStmt): 
                                                condition(Type=BinaryExpr): 
                                                    operator: "GREATER"
                                                    left(Type=NameExpr): 
                                                        name(Type=SimpleName): 
                                                            identifier: "x"
                                                    right(Type=IntegerLiteralExpr): 
                                                        value: "1"
                                                elseStmt(Type=BlockStmt): 
                                                    statements: 
                                                        - statement(Type=ExpressionStmt): 
                                                            expression(Type=AssignExpr): 
                                                                operator: "ASSIGN"
                                                                target(Type=NameExpr): 
                                                                    name(Type=SimpleName): 
                                                                        identifier: "x"
                                                                value(Type=IntegerLiteralExpr): 
                                                                    value: "212"
                                                thenStmt(Type=BlockStmt): 
                                                    statements: 
                                                        - statement(Type=ExpressionStmt): 
                                                            expression(Type=AssignExpr): 
                                                                operator: "ASSIGN"
                                                                target(Type=NameExpr): 
                                                                    name(Type=SimpleName): 
                                                                        identifier: "x"
                                                                value(Type=IntegerLiteralExpr): 
                                                                    value: "211"
                                thenStmt(Type=BlockStmt): 
                                    statements: 
                                        - statement(Type=ExpressionStmt): 
                                            expression(Type=AssignExpr): 
                                                operator: "ASSIGN"
                                                target(Type=NameExpr): 
                                                    name(Type=SimpleName): 
                                                        identifier: "x"
                                                value(Type=IntegerLiteralExpr): 
                                                    value: "21" 

And these are the lines of code that correspond to this AST:

            "   if (x>4) {  " +
            "      x=21;  " +
            "    }  " +
            "    else if (x<4) {  " +
            "      if (x>1) {  " +
            "        x=211;  " +
            "      }  " +
            "      else {  " +
            "        x=212;  " +
            "      }  " +
            "    }  " +

Regarding the AST, the first line implies that this Statement is an IfStmt statement(Type=IfStmt)

All of the following thenStmt and elseStmt are of type BlockStmt except the else if which is of type IfStmt. What I do not understand is why this specifically is assigned to a different type than the rest of thenStmt and elseStmt. And a result, I have the not a BlockStmt error in my output.

@matozoid
Copy link
Contributor

matozoid commented Apr 23, 2019

Try outputting the AST's for these code fragments:
if(true)a=1;
if(true){a=1;}
if(true)a=1; else a=2;
if(true)a=1; else {a=2;}
and just for fun:
if(true);else;

@is742 is742 changed the title How can I access the contents of my code (loops, if statements, etc) in the order they appear How can I access the contents of my code? Apr 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question (JP usage) How to use JP to inspect/modify the AST - please close your question once answered!
Projects
None yet
Development

No branches or pull requests

2 participants