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 java.util.ArrayList;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
11  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
12  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
13  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
14  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
15  import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
16  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
17  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
18  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
19  import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
20  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
21  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
22  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
23  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
24  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
25  
26  /**
27   * @author Eric Olander
28   * @author Wouter Zelle
29   * @since Created on April 17, 2005, 9:49 PM
30   */
31  public class SingularFieldRule extends AbstractJavaRule {
32  
33  	/**
34  	 * Restore old behavior by setting both properties to true, which will result in many false positives
35  	 */
36      private static final BooleanProperty CHECK_INNER_CLASSES = new BooleanProperty(
37  			"checkInnerClasses", "Check inner classes", false, 1.0f);
38      private static final BooleanProperty DISALLOW_NOT_ASSIGNMENT = new BooleanProperty(
39  			"disallowNotAssignment", "Disallow violations where the first usage is not an assignment", false, 2.0f);
40  
41      public SingularFieldRule() {
42  	definePropertyDescriptor(CHECK_INNER_CLASSES);
43  	definePropertyDescriptor(DISALLOW_NOT_ASSIGNMENT);
44      }
45      
46      @SuppressWarnings("PMD.CompareObjectsWithEquals")
47      @Override
48      public Object visit(ASTFieldDeclaration node, Object data) {
49      	boolean checkInnerClasses = getProperty(CHECK_INNER_CLASSES);
50      	boolean disallowNotAssignment = getProperty(DISALLOW_NOT_ASSIGNMENT);
51  
52          if (node.isPrivate() && !node.isStatic()) {
53              for (ASTVariableDeclarator declarator: node.findChildrenOfType(ASTVariableDeclarator.class)) {
54          	ASTVariableDeclaratorId declaration = (ASTVariableDeclaratorId) declarator.jjtGetChild(0);
55                  List<NameOccurrence> usages = declaration.getUsages();
56                  Node decl = null;
57                  boolean violation = true;
58                  for (int ix = 0; ix < usages.size(); ix++) {
59                      NameOccurrence no = usages.get(ix);
60                      Node location = no.getLocation();
61  
62                      ASTPrimaryExpression primaryExpressionParent = location.getFirstParentOfType(ASTPrimaryExpression.class);
63                      if (ix==0 && !disallowNotAssignment) {
64                      	if (primaryExpressionParent.getFirstParentOfType(ASTIfStatement.class) != null) {
65                      		//the first usage is in an if, so it may be skipped on
66                      		//later calls to the method. So this might be legit code
67                      		//that simply stores an object for later use.
68                      		violation = false;
69      	                	break;		//Optimization
70                      	}
71  
72                      	//Is the first usage in an assignment?
73                      	Node potentialStatement = primaryExpressionParent.jjtGetParent();
74      	                boolean assignmentToField = no.getImage().equals(location.getImage());	//Check the the assignment is not to a field inside the field object
75      					if (!assignmentToField || !isInAssignment(potentialStatement)) {
76      	                	violation = false;
77      	                	break;		//Optimization
78      	                } else {
79      	                	if (usages.size() > ix + 1) {
80      	                	    Node secondUsageLocation = usages.get(ix + 1).getLocation();
81  
82      	                		List<ASTStatementExpression> parentStatements = secondUsageLocation.getParentsOfType(ASTStatementExpression.class);
83      	                		for (ASTStatementExpression statementExpression : parentStatements) {
84      	                			if (statementExpression != null && statementExpression.equals(potentialStatement)) {
85      		                			//The second usage is in the assignment of the first usage, which is allowed
86      		                			violation = false;
87      		    	                	break;		//Optimization
88      		                		}
89      							}
90  
91      	                	}
92      	                }
93                      }
94  
95                      if (!checkInnerClasses) {
96      	                //Skip inner classes because the field can be used in the outer class and checking this is too difficult
97      	                ASTClassOrInterfaceDeclaration clazz = location.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class);
98      	                if (clazz!= null && clazz.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class) != null) {
99      	                	violation = false;
100     	                	break;			//Optimization
101     	                }
102                     }
103 
104                     if (primaryExpressionParent.jjtGetParent() instanceof ASTSynchronizedStatement) {
105                     	//This usage is directly in an expression of a synchronized block
106                     	violation = false;
107                     	break;			//Optimization
108                     }
109 
110                     Node method = location.getFirstParentOfType(ASTMethodDeclaration.class);
111                     if (method == null) {
112                         method = location.getFirstParentOfType(ASTConstructorDeclaration.class);
113                         if (method == null) {
114                         	method = location.getFirstParentOfType(ASTInitializer.class);
115                         	if (method == null) {
116                         		continue;
117                         	}
118                         }
119                     }
120 
121                     if (decl == null) {
122                         decl = method;
123                         continue;
124                     } else if (decl != method
125                             // handle inner classes
126                             && decl.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class)
127                                 == method.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class)) {
128                         violation = false;
129                         break;			//Optimization
130                     }
131 
132 
133                 }
134 
135                 if (violation && !usages.isEmpty()) {
136                     addViolation(data, node, new Object[] { declaration.getImage() });
137                 }
138             }
139         }
140         return data;
141     }
142 
143 	private boolean isInAssignment(Node potentialStatement) {
144 		if (potentialStatement instanceof ASTStatementExpression) {
145 			ASTStatementExpression statement = (ASTStatementExpression)potentialStatement;
146 			List<ASTAssignmentOperator> assignments = new ArrayList<ASTAssignmentOperator>();
147 			statement.findDescendantsOfType(ASTAssignmentOperator.class, assignments, false);
148 			return !assignments.isEmpty() && "=".equals(assignments.get(0).getImage());
149 		} else {
150 			return false;
151 		}
152 	}
153 }