SingularFieldRule.java
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 = (ASTVariableDeclaratorIddeclarator.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==&& !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 }