com.puppycrawl.tools.checkstyle.checks.coding.DeclarationOrderCheck Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checkstyle Show documentation
Show all versions of checkstyle Show documentation
Checkstyle is a development tool to help programmers write Java code
that adheres to a coding standard
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2016 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.coding;
import java.util.Deque;
import java.util.Set;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
/**
* Checks that the parts of a class or interface declaration
* appear in the order suggested by the
*
* Code Conventions for the Java Programming Language.
*
*
*
* - Class (static) variables. First the public class variables, then
* the protected, then package level (no access modifier), and then
* the private.
* - Instance variables. First the public class variables, then
* the protected, then package level (no access modifier), and then
* the private.
* - Constructors
* - Methods
*
*
* ATTENTION: the check skips class fields which have
*
* forward references from validation due to the fact that we have Checkstyle's limitations
* to clearly detect user intention of fields location and grouping. For example,
*
{@code
* public class A {
* private double x = 1.0;
* private double y = 2.0;
* public double slope = x / y; // will be skipped from validation due to forward reference
* }
* }
*
* Available options:
*
* - ignoreModifiers
* - ignoreConstructors
*
*
* Purpose of ignore* option is to ignore related violations,
* however it still impacts on other class members.
*
*
For example:
*
{@code
* class K {
* int a;
* void m(){}
* K(){} <-- "Constructor definition in wrong order"
* int b; <-- "Instance variable definition in wrong order"
* }
* }
*
* With ignoreConstructors option:
*
{@code
* class K {
* int a;
* void m(){}
* K(){}
* int b; <-- "Instance variable definition in wrong order"
* }
* }
*
* With ignoreConstructors option and without a method definition in a source class:
*
{@code
* class K {
* int a;
* K(){}
* int b; <-- "Instance variable definition in wrong order"
* }
* }
*
* An example of how to configure the check is:
*
*
* <module name="DeclarationOrder"/>
*
*
* @author r_auckenthaler
*/
public class DeclarationOrderCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_CONSTRUCTOR = "declaration.order.constructor";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_STATIC = "declaration.order.static";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_INSTANCE = "declaration.order.instance";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_ACCESS = "declaration.order.access";
/** State for the VARIABLE_DEF. */
private static final int STATE_STATIC_VARIABLE_DEF = 1;
/** State for the VARIABLE_DEF. */
private static final int STATE_INSTANCE_VARIABLE_DEF = 2;
/** State for the CTOR_DEF. */
private static final int STATE_CTOR_DEF = 3;
/** State for the METHOD_DEF. */
private static final int STATE_METHOD_DEF = 4;
/**
* List of Declaration States. This is necessary due to
* inner classes that have their own state.
*/
private Deque scopeStates;
/** Set of all class field names.*/
private Set classFieldNames;
/** If true, ignores the check to constructors. */
private boolean ignoreConstructors;
/** If true, ignore the check to modifiers (fields, ...). */
private boolean ignoreModifiers;
@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.MODIFIERS,
TokenTypes.OBJBLOCK,
TokenTypes.VARIABLE_DEF,
};
}
@Override
public int[] getRequiredTokens() {
return getAcceptableTokens();
}
@Override
public void beginTree(DetailAST rootAST) {
scopeStates = Queues.newArrayDeque();
classFieldNames = Sets.newHashSet();
}
@Override
public void visitToken(DetailAST ast) {
final int parentType = ast.getParent().getType();
switch (ast.getType()) {
case TokenTypes.OBJBLOCK:
scopeStates.push(new ScopeState());
break;
case TokenTypes.MODIFIERS:
if (parentType == TokenTypes.VARIABLE_DEF
&& ast.getParent().getParent().getType() == TokenTypes.OBJBLOCK) {
processModifiers(ast);
}
break;
case TokenTypes.CTOR_DEF:
if (parentType == TokenTypes.OBJBLOCK) {
processConstructor(ast);
}
break;
case TokenTypes.METHOD_DEF:
if (parentType == TokenTypes.OBJBLOCK) {
final ScopeState state = scopeStates.peek();
// nothing can be bigger than method's state
state.currentScopeState = STATE_METHOD_DEF;
}
break;
case TokenTypes.VARIABLE_DEF:
if (ScopeUtils.isClassFieldDef(ast)) {
final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT);
classFieldNames.add(fieldDef.getText());
}
break;
default:
break;
}
}
/**
* Processes constructor.
* @param ast constructor AST.
*/
private void processConstructor(DetailAST ast) {
final ScopeState state = scopeStates.peek();
if (state.currentScopeState > STATE_CTOR_DEF) {
if (!ignoreConstructors) {
log(ast, MSG_CONSTRUCTOR);
}
}
else {
state.currentScopeState = STATE_CTOR_DEF;
}
}
/**
* Processes modifiers.
* @param ast ast of Modifiers.
*/
private void processModifiers(DetailAST ast) {
final ScopeState state = scopeStates.peek();
final boolean isStateValid = processModifiersState(ast, state);
processModifiersSubState(ast, state, isStateValid);
}
/**
* Process if given modifiers are appropriate in given state
* ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF},
* ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is
* it updates states where appropriate or logs violation.
* @param modifierAst modifiers to process
* @param state current state
* @return true if modifierAst is valid in given state, false otherwise
*/
private boolean processModifiersState(DetailAST modifierAst, ScopeState state) {
boolean isStateValid = true;
if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) {
isStateValid = false;
log(modifierAst, MSG_INSTANCE);
}
else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) {
state.declarationAccess = Scope.PUBLIC;
state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF;
}
}
else {
if (state.currentScopeState > STATE_STATIC_VARIABLE_DEF) {
if (!ignoreModifiers
|| state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) {
isStateValid = false;
log(modifierAst, MSG_STATIC);
}
}
else {
state.currentScopeState = STATE_STATIC_VARIABLE_DEF;
}
}
return isStateValid;
}
/**
* Checks if given modifiers are valid in substate of given
* state({@code Scope}), if it is it updates substate or else it
* logs violation.
* @param modifiersAst modifiers to process
* @param state curent state
* @param isStateValid is main state for given modifiers is valid
*/
private void processModifiersSubState(DetailAST modifiersAst, ScopeState state,
boolean isStateValid) {
final Scope access = ScopeUtils.getScopeFromMods(modifiersAst);
if (state.declarationAccess.compareTo(access) > 0) {
if (isStateValid
&& !ignoreModifiers
&& !isForwardReference(modifiersAst.getParent())) {
log(modifiersAst, MSG_ACCESS);
}
}
else {
state.declarationAccess = access;
}
}
/**
* Checks whether an identifier references a field which has been already defined in class.
* @param fieldDef a field definition.
* @return true if an identifier references a field which has been already defined in class.
*/
private boolean isForwardReference(DetailAST fieldDef) {
final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT);
final Set exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT);
boolean forwardReference = false;
for (DetailAST ident : exprIdents) {
if (classFieldNames.contains(ident.getText())) {
forwardReference = true;
break;
}
}
return forwardReference;
}
/**
* Collects all tokens of specific type starting with the current ast node.
* @param ast ast node.
* @param tokenType token type.
* @return a set of all tokens of specific type starting with the current ast node.
*/
private static Set getAllTokensOfType(DetailAST ast, int tokenType) {
DetailAST vertex = ast;
final Set result = Sets.newHashSet();
final Deque stack = Queues.newArrayDeque();
while (vertex != null || !stack.isEmpty()) {
if (!stack.isEmpty()) {
vertex = stack.pop();
}
while (vertex != null) {
if (vertex.getType() == tokenType && !vertex.equals(ast)) {
result.add(vertex);
}
if (vertex.getNextSibling() != null) {
stack.push(vertex.getNextSibling());
}
vertex = vertex.getFirstChild();
}
}
return result;
}
@Override
public void leaveToken(DetailAST ast) {
if (ast.getType() == TokenTypes.OBJBLOCK) {
scopeStates.pop();
}
}
/**
* Sets whether to ignore constructors.
* @param ignoreConstructors whether to ignore constructors.
*/
public void setIgnoreConstructors(boolean ignoreConstructors) {
this.ignoreConstructors = ignoreConstructors;
}
/**
* Sets whether to ignore modifiers.
* @param ignoreModifiers whether to ignore modifiers.
*/
public void setIgnoreModifiers(boolean ignoreModifiers) {
this.ignoreModifiers = ignoreModifiers;
}
/**
* Private class to encapsulate the state.
*/
private static class ScopeState {
/** The state the check is in. */
private int currentScopeState = STATE_STATIC_VARIABLE_DEF;
/** The sub-state the check is in. */
private Scope declarationAccess = Scope.PUBLIC;
}
}