/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.design; import java.util.ArrayList; import java.util.List; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.rule.AbstractLombokAwareRule; import net.sourceforge.pmd.lang.rule.properties.BooleanProperty; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; /** * @author Eric Olander * @author Wouter Zelle * @since Created on April 17, 2005, 9:49 PM */ public class SingularFieldRule extends AbstractLombokAwareRule { /** * Restore old behavior by setting both properties to true, which will * result in many false positives */ private static final BooleanProperty CHECK_INNER_CLASSES = new BooleanProperty("checkInnerClasses", "Check inner classes", false, 1.0f); private static final BooleanProperty DISALLOW_NOT_ASSIGNMENT = new BooleanProperty("disallowNotAssignment", "Disallow violations where the first usage is not an assignment", false, 2.0f); public SingularFieldRule() { definePropertyDescriptor(CHECK_INNER_CLASSES); definePropertyDescriptor(DISALLOW_NOT_ASSIGNMENT); } @SuppressWarnings("PMD.CompareObjectsWithEquals") @Override public Object visit(ASTFieldDeclaration node, Object data) { boolean checkInnerClasses = getProperty(CHECK_INNER_CLASSES); boolean disallowNotAssignment = getProperty(DISALLOW_NOT_ASSIGNMENT); if (node.isPrivate() && !node.isStatic() && !hasClassLombokAnnotation() && !hasLombokAnnotation(node)) { for (ASTVariableDeclarator declarator : node.findChildrenOfType(ASTVariableDeclarator.class)) { ASTVariableDeclaratorId declaration = (ASTVariableDeclaratorId) declarator.jjtGetChild(0); List<NameOccurrence> usages = declaration.getUsages(); Node decl = null; boolean violation = true; for (int ix = 0; ix < usages.size(); ix++) { NameOccurrence no = usages.get(ix); Node location = no.getLocation(); ASTPrimaryExpression primaryExpressionParent = location .getFirstParentOfType(ASTPrimaryExpression.class); if (ix == 0 && !disallowNotAssignment) { if (primaryExpressionParent.getFirstParentOfType(ASTIfStatement.class) != null) { // the first usage is in an if, so it may be skipped // on // later calls to the method. So this might be legit // code // that simply stores an object for later use. violation = false; break; // Optimization } // Is the first usage in an assignment? Node potentialStatement = primaryExpressionParent.jjtGetParent(); // Check that the assignment is not to a field inside // the field object boolean assignmentToField = no.getImage().equals(location.getImage()); if (!assignmentToField || !isInAssignment(potentialStatement)) { violation = false; break; // Optimization } else { if (usages.size() > ix + 1) { Node secondUsageLocation = usages.get(ix + 1).getLocation(); List<ASTStatementExpression> parentStatements = secondUsageLocation .getParentsOfType(ASTStatementExpression.class); for (ASTStatementExpression statementExpression : parentStatements) { if (statementExpression != null && statementExpression.equals(potentialStatement)) { // The second usage is in the assignment // of the first usage, which is allowed violation = false; break; // Optimization } } } } } if (!checkInnerClasses) { // Skip inner classes because the field can be used in // the outer class and checking this is too difficult ASTClassOrInterfaceDeclaration clazz = location .getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); if (clazz != null && clazz.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class) != null) { violation = false; break; // Optimization } } if (primaryExpressionParent.jjtGetParent() instanceof ASTSynchronizedStatement) { // This usage is directly in an expression of a // synchronized block violation = false; break; // Optimization } if (location.getFirstParentOfType(ASTLambdaExpression.class) != null) { // This usage is inside a lambda expression violation = false; break; // Optimization } Node method = location.getFirstParentOfType(ASTMethodDeclaration.class); if (method == null) { method = location.getFirstParentOfType(ASTConstructorDeclaration.class); if (method == null) { method = location.getFirstParentOfType(ASTInitializer.class); if (method == null) { continue; } } } if (decl == null) { decl = method; continue; } else if (decl != method // handle inner classes && decl.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class) == method .getFirstParentOfType(ASTClassOrInterfaceDeclaration.class)) { violation = false; break; // Optimization } } if (violation && !usages.isEmpty()) { addViolation(data, node, new Object[] { declaration.getImage() }); } } } return data; } private boolean isInAssignment(Node potentialStatement) { if (potentialStatement instanceof ASTStatementExpression) { ASTStatementExpression statement = (ASTStatementExpression) potentialStatement; List<ASTAssignmentOperator> assignments = new ArrayList<>(); statement.findDescendantsOfType(ASTAssignmentOperator.class, assignments, false); return !assignments.isEmpty() && "=".equals(assignments.get(0).getImage()); } else { return false; } } }