View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.design;
5   
6   import net.sourceforge.pmd.lang.ast.Node;
7   import net.sourceforge.pmd.lang.java.ast.ASTBlock;
8   import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral;
9   import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
10  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
11  import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
12  import net.sourceforge.pmd.lang.java.ast.ASTResultType;
13  import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
14  import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus;
15  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
16  
17  public class SimplifyBooleanReturnsRule extends AbstractJavaRule {
18  
19      public Object visit(ASTMethodDeclaration node, Object data) {
20          // only boolean methods should be inspected
21          ASTResultType r = node.getResultType();
22  
23          if (!r.isVoid()) {
24              Node t = r.jjtGetChild(0);
25              if (t.jjtGetNumChildren() == 1) {
26                  t = t.jjtGetChild(0);
27                  if (t instanceof ASTPrimitiveType && ((ASTPrimitiveType) t).isBoolean()) {
28                      return super.visit(node, data);
29                  }
30              }
31          }
32          // skip method
33          return data;
34      }
35  
36      public Object visit(ASTIfStatement node, Object data) {
37          // that's the case: if..then..return; return;
38          if (!node.hasElse() && isIfJustReturnsBoolean(node) && isJustReturnsBooleanAfter(node)) {
39              addViolation(data, node);
40              return super.visit(node, data);
41          }
42  
43          // only deal with if..then..else stmts
44          if (node.jjtGetNumChildren() != 3) {
45              return super.visit(node, data);
46          }
47  
48          // don't bother if either the if or the else block is empty
49          if (node.jjtGetChild(1).jjtGetNumChildren() == 0 || node.jjtGetChild(2).jjtGetNumChildren() == 0) {
50              return super.visit(node, data);
51          }
52  
53          Node returnStatement1 = node.jjtGetChild(1).jjtGetChild(0);
54          Node returnStatement2 = node.jjtGetChild(2).jjtGetChild(0);
55  
56          if (returnStatement1 instanceof ASTReturnStatement && returnStatement2 instanceof ASTReturnStatement) {
57              Node expression1 = returnStatement1.jjtGetChild(0).jjtGetChild(0);
58              Node expression2 = returnStatement2.jjtGetChild(0).jjtGetChild(0);
59              if (terminatesInBooleanLiteral(returnStatement1) && terminatesInBooleanLiteral(returnStatement2)) {
60                  addViolation(data, node);
61              } else if (expression1 instanceof ASTUnaryExpressionNotPlusMinus
62                      ^ expression2 instanceof ASTUnaryExpressionNotPlusMinus) {
63                  // We get the nodes under the '!' operator
64                  // If they are the same => error
65                  if (isNodesEqualWithUnaryExpression(expression1, expression2)) {
66                      // second case:
67                      // If
68                      // Expr
69                      // Statement
70                      // ReturnStatement
71                      // UnaryExpressionNotPlusMinus '!'
72                      // Expression E
73                      // Statement
74                      // ReturnStatement
75                      // Expression E
76                      // i.e.,
77                      // if (foo)
78                      // return !a;
79                      // else
80                      // return a;
81                      addViolation(data, node);
82                  }
83              }
84          } else if (hasOneBlockStmt(node.jjtGetChild(1)) && hasOneBlockStmt(node.jjtGetChild(2))) {
85              // We have blocks so we must go down three levels (BlockStatement,
86              // Statement, ReturnStatement)
87              returnStatement1 = returnStatement1.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
88              returnStatement2 = returnStatement2.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
89  
90              // if we have 2 return;
91              if (isSimpleReturn(returnStatement1) && isSimpleReturn(returnStatement2)) {
92                  // third case
93                  // If
94                  // Expr
95                  // Statement
96                  // Block
97                  // BlockStatement
98                  // Statement
99                  // ReturnStatement
100                 // Statement
101                 // Block
102                 // BlockStatement
103                 // Statement
104                 // ReturnStatement
105                 // i.e.,
106                 // if (foo) {
107                 // return true;
108                 // } else {
109                 // return false;
110                 // }
111                 addViolation(data, node);
112             } else {
113                 Node expression1 = getDescendant(returnStatement1, 4);
114                 Node expression2 = getDescendant(returnStatement2, 4);
115                 if (terminatesInBooleanLiteral(node.jjtGetChild(1).jjtGetChild(0))
116                         && terminatesInBooleanLiteral(node.jjtGetChild(2).jjtGetChild(0))) {
117                     addViolation(data, node);
118                 } else if (expression1 instanceof ASTUnaryExpressionNotPlusMinus
119                         ^ expression2 instanceof ASTUnaryExpressionNotPlusMinus) {
120                     // We get the nodes under the '!' operator
121                     // If they are the same => error
122                     if (isNodesEqualWithUnaryExpression(expression1, expression2)) {
123                         // forth case
124                         // If
125                         // Expr
126                         // Statement
127                         // Block
128                         // BlockStatement
129                         // Statement
130                         // ReturnStatement
131                         // UnaryExpressionNotPlusMinus '!'
132                         // Expression E
133                         // Statement
134                         // Block
135                         // BlockStatement
136                         // Statement
137                         // ReturnStatement
138                         // Expression E
139                         // i.e.,
140                         // if (foo) {
141                         // return !a;
142                         // } else {
143                         // return a;
144                         // }
145                         addViolation(data, node);
146                     }
147                 }
148             }
149         }
150         return super.visit(node, data);
151     }
152 
153     /**
154      * Checks, whether there is a statement after the given if statement, and if
155      * so, whether this is just a return boolean statement.
156      * 
157      * @param node
158      *            the if statement
159      * @return
160      */
161     private boolean isJustReturnsBooleanAfter(ASTIfStatement ifNode) {
162         Node blockStatement = ifNode.jjtGetParent().jjtGetParent();
163         Node block = blockStatement.jjtGetParent();
164         if (block.jjtGetNumChildren() != blockStatement.jjtGetChildIndex() + 1 + 1) {
165             return false;
166         }
167 
168         Node nextBlockStatement = block.jjtGetChild(blockStatement.jjtGetChildIndex() + 1);
169         return terminatesInBooleanLiteral(nextBlockStatement);
170     }
171 
172     /**
173      * Checks whether the given ifstatement just returns a boolean in the if
174      * clause.
175      * 
176      * @param node
177      *            the if statement
178      * @return
179      */
180     private boolean isIfJustReturnsBoolean(ASTIfStatement ifNode) {
181         Node node = ifNode.jjtGetChild(1);
182         return node.jjtGetNumChildren() == 1
183                 && (hasOneBlockStmt(node) || terminatesInBooleanLiteral(node.jjtGetChild(0)));
184     }
185 
186     private boolean hasOneBlockStmt(Node node) {
187         return node.jjtGetChild(0) instanceof ASTBlock && node.jjtGetChild(0).jjtGetNumChildren() == 1
188                 && terminatesInBooleanLiteral(node.jjtGetChild(0).jjtGetChild(0));
189     }
190 
191     /**
192      * Returns the first child node going down 'level' levels or null if level
193      * is invalid
194      */
195     private Node getDescendant(Node node, int level) {
196         Node n = node;
197         for (int i = 0; i < level; i++) {
198             if (n.jjtGetNumChildren() == 0) {
199                 return null;
200             }
201             n = n.jjtGetChild(0);
202         }
203         return n;
204     }
205 
206     private boolean terminatesInBooleanLiteral(Node node) {
207         return eachNodeHasOneChild(node) && getLastChild(node) instanceof ASTBooleanLiteral;
208     }
209 
210     private boolean eachNodeHasOneChild(Node node) {
211         if (node.jjtGetNumChildren() > 1) {
212             return false;
213         }
214         if (node.jjtGetNumChildren() == 0) {
215             return true;
216         }
217         return eachNodeHasOneChild(node.jjtGetChild(0));
218     }
219 
220     private Node getLastChild(Node node) {
221         if (node.jjtGetNumChildren() == 0) {
222             return node;
223         }
224         return getLastChild(node.jjtGetChild(0));
225     }
226 
227     private boolean isNodesEqualWithUnaryExpression(Node n1, Node n2) {
228         Node node1;
229         Node node2;
230         if (n1 instanceof ASTUnaryExpressionNotPlusMinus) {
231             node1 = n1.jjtGetChild(0);
232         } else {
233             node1 = n1;
234         }
235         if (n2 instanceof ASTUnaryExpressionNotPlusMinus) {
236             node2 = n2.jjtGetChild(0);
237         } else {
238             node2 = n2;
239         }
240         return isNodesEquals(node1, node2);
241     }
242 
243     private boolean isNodesEquals(Node n1, Node n2) {
244         int numberChild1 = n1.jjtGetNumChildren();
245         int numberChild2 = n2.jjtGetNumChildren();
246         if (numberChild1 != numberChild2) {
247             return false;
248         }
249         if (!n1.getClass().equals(n2.getClass())) {
250             return false;
251         }
252         if (!n1.toString().equals(n2.toString())) {
253             return false;
254         }
255         for (int i = 0; i < numberChild1; i++) {
256             if (!isNodesEquals(n1.jjtGetChild(i), n2.jjtGetChild(i))) {
257                 return false;
258             }
259         }
260         return true;
261     }
262 
263     private boolean isSimpleReturn(Node node) {
264         return node instanceof ASTReturnStatement && node.jjtGetNumChildren() == 0;
265     }
266 
267 }