/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.design; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.rule.properties.BooleanProperty; /** * Detects fields that are declared after methods, constructors, etc. It was a * XPath rule, but the Java version is much faster. The XPath rule for * reference: * * <pre> //ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/FieldDeclaration [not(.//ClassOrInterfaceBodyDeclaration) or $ignoreAnonymousClassDeclarations = 'false'] [../preceding-sibling::ClassOrInterfaceBodyDeclaration [ count(ClassOrInterfaceDeclaration) > 0 or count(ConstructorDeclaration) > 0 or count(MethodDeclaration) > 0 or count(AnnotationTypeDeclaration) > 0 or ($ignoreEnumDeclarations = 'false' and count(EnumDeclaration) > 0) ] ] * </pre> */ public class FieldDeclarationsShouldBeAtStartOfClassRule extends AbstractJavaRule { private BooleanProperty ignoreEnumDeclarations = new BooleanProperty("ignoreEnumDeclarations", "Ignore Enum Declarations that precede fields.", true, 1.0f); private BooleanProperty ignoreAnonymousClassDeclarations = new BooleanProperty("ignoreAnonymousClassDeclarations", "Ignore Field Declarations, that are initialized with anonymous class declarations", true, 2.0f); private BooleanProperty ignoreInterfaceDeclarations = new BooleanProperty("ignoreInterfaceDeclarations", "Ignore Interface Declarations that precede fields.", false, 3.0f); /** * Initializes the rule {@link FieldDeclarationsShouldBeAtStartOfClassRule}. */ public FieldDeclarationsShouldBeAtStartOfClassRule() { definePropertyDescriptor(ignoreEnumDeclarations); definePropertyDescriptor(ignoreAnonymousClassDeclarations); definePropertyDescriptor(ignoreInterfaceDeclarations); } @Override public Object visit(ASTFieldDeclaration node, Object data) { Node parent = node.jjtGetParent().jjtGetParent(); for (int i = 0; i < parent.jjtGetNumChildren(); i++) { Node child = parent.jjtGetChild(i); if (child.jjtGetNumChildren() > 0) { child = skipAnnotations(child); } if (child.equals(node)) { break; } if (child instanceof ASTFieldDeclaration) { continue; } if (node.hasDescendantOfType(ASTClassOrInterfaceBodyDeclaration.class) && getProperty(ignoreAnonymousClassDeclarations).booleanValue()) { continue; } if (child instanceof ASTMethodDeclaration || child instanceof ASTConstructorDeclaration || child instanceof ASTAnnotationTypeDeclaration) { addViolation(data, node); break; } if (child instanceof ASTClassOrInterfaceDeclaration) { ASTClassOrInterfaceDeclaration declaration = (ASTClassOrInterfaceDeclaration) child; if (declaration.isInterface() && getProperty(ignoreInterfaceDeclarations).booleanValue()) { continue; } else { addViolation(data, node); break; } } if (child instanceof ASTEnumDeclaration && !getProperty(ignoreEnumDeclarations).booleanValue()) { addViolation(data, node); break; } } return data; } /** * Ignore all annotations, until anything, that is not an annotation and * return this node * * @param child * the node from where to start the search * @return the first child or the first child after annotations */ private Node skipAnnotations(Node child) { Node nextChild = child.jjtGetChild(0); for (int j = 0; j < child.jjtGetNumChildren(); j++) { if (!(child.jjtGetChild(j) instanceof ASTAnnotation)) { nextChild = child.jjtGetChild(j); break; } } return nextChild; } }