View Javadoc

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