001 /*
002 * SingularField.java
003 *
004 * Created on April 17, 2005, 9:49 PM
005 */
006 package net.sourceforge.pmd.lang.java.rule.design;
007
008 import java.util.ArrayList;
009 import java.util.List;
010
011 import net.sourceforge.pmd.lang.ast.Node;
012 import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
013 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
014 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
017 import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
018 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
019 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
020 import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
021 import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
022 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
023 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
024 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
025 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
026 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
027
028 /**
029 * @author Eric Olander
030 * @author Wouter Zelle
031 */
032 public class SingularFieldRule extends AbstractJavaRule {
033
034 /**
035 * Restore old behavior by setting both properties to true, which will result in many false positives
036 */
037 private static final BooleanProperty CHECK_INNER_CLASSES = new BooleanProperty(
038 "checkInnerClasses", "Check inner classes", false, 1.0f);
039 private static final BooleanProperty DISALLOW_NOT_ASSIGNMENT = new BooleanProperty(
040 "disallowNotAssignment", "Disallow violations where the first usage is not an assignment", false, 2.0f);
041
042 public SingularFieldRule() {
043 definePropertyDescriptor(CHECK_INNER_CLASSES);
044 definePropertyDescriptor(DISALLOW_NOT_ASSIGNMENT);
045 }
046
047 @SuppressWarnings("PMD.CompareObjectsWithEquals")
048 @Override
049 public Object visit(ASTFieldDeclaration node, Object data) {
050 boolean checkInnerClasses = getProperty(CHECK_INNER_CLASSES);
051 boolean disallowNotAssignment = getProperty(DISALLOW_NOT_ASSIGNMENT);
052
053 if (node.isPrivate() && !node.isStatic()) {
054 for (ASTVariableDeclarator declarator: node.findChildrenOfType(ASTVariableDeclarator.class)) {
055 ASTVariableDeclaratorId declaration = (ASTVariableDeclaratorId) declarator.jjtGetChild(0);
056 List<NameOccurrence> usages = declaration.getUsages();
057 Node decl = null;
058 boolean violation = true;
059 for (int ix = 0; ix < usages.size(); ix++) {
060 NameOccurrence no = usages.get(ix);
061 Node location = no.getLocation();
062
063 ASTPrimaryExpression primaryExpressionParent = location.getFirstParentOfType(ASTPrimaryExpression.class);
064 if (ix==0 && !disallowNotAssignment) {
065 if (primaryExpressionParent.getFirstParentOfType(ASTIfStatement.class) != null) {
066 //the first usage is in an if, so it may be skipped on
067 //later calls to the method. So this might be legit code
068 //that simply stores an object for later use.
069 violation = false;
070 break; //Optimization
071 }
072
073 //Is the first usage in an assignment?
074 Node potentialStatement = primaryExpressionParent.jjtGetParent();
075 boolean assignmentToField = no.getImage().equals(location.getImage()); //Check the the assignment is not to a field inside the field object
076 if (!assignmentToField || !isInAssignment(potentialStatement)) {
077 violation = false;
078 break; //Optimization
079 } else {
080 if (usages.size() > ix + 1) {
081 Node secondUsageLocation = usages.get(ix + 1).getLocation();
082
083 List<ASTStatementExpression> parentStatements = secondUsageLocation.getParentsOfType(ASTStatementExpression.class);
084 for (ASTStatementExpression statementExpression : parentStatements) {
085 if (statementExpression != null && statementExpression.equals(potentialStatement)) {
086 //The second usage is in the assignment of the first usage, which is allowed
087 violation = false;
088 break; //Optimization
089 }
090 }
091
092 }
093 }
094 }
095
096 if (!checkInnerClasses) {
097 //Skip inner classes because the field can be used in the outer class and checking this is too difficult
098 ASTClassOrInterfaceDeclaration clazz = location.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class);
099 if (clazz!= null && clazz.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class) != null) {
100 violation = false;
101 break; //Optimization
102 }
103 }
104
105 if (primaryExpressionParent.jjtGetParent() instanceof ASTSynchronizedStatement) {
106 //This usage is directly in an expression of a synchronized block
107 violation = false;
108 break; //Optimization
109 }
110
111 Node method = location.getFirstParentOfType(ASTMethodDeclaration.class);
112 if (method == null) {
113 method = location.getFirstParentOfType(ASTConstructorDeclaration.class);
114 if (method == null) {
115 method = location.getFirstParentOfType(ASTInitializer.class);
116 if (method == null) {
117 continue;
118 }
119 }
120 }
121
122 if (decl == null) {
123 decl = method;
124 continue;
125 } else if (decl != method) {
126 violation = false;
127 break; //Optimization
128 }
129
130
131 }
132
133 if (violation && !usages.isEmpty()) {
134 addViolation(data, node, new Object[] { declaration.getImage() });
135 }
136 }
137 }
138 return data;
139 }
140
141 private boolean isInAssignment(Node potentialStatement) {
142 if (potentialStatement instanceof ASTStatementExpression) {
143 ASTStatementExpression statement = (ASTStatementExpression)potentialStatement;
144 List<ASTAssignmentOperator> assignments = new ArrayList<ASTAssignmentOperator>();
145 statement.findDescendantsOfType(ASTAssignmentOperator.class, assignments, false);
146 return !assignments.isEmpty() && "=".equals(assignments.get(0).getImage());
147 } else {
148 return false;
149 }
150 }
151 }
|