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