View Javadoc

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