////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 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.metrics;
import java.util.ArrayDeque;
import java.util.Deque;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
/**
* Restricts nested boolean operators (&&, ||, &, | and ^) to
* a specified depth (default = 3).
* Note: &, | and ^ are not checked if they are part of constructor or
* method call because they can be applied to non boolean variables and
* Checkstyle does not know types of methods from different classes.
*
* @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
* @author o_sukhodolsky
*/
public final class BooleanExpressionComplexityCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "booleanExpressionComplexity";
/** Default allowed complexity. */
private static final int DEFAULT_MAX = 3;
/** Stack of contexts. */
private final Deque<Context> contextStack = new ArrayDeque<>();
/** Maximum allowed complexity. */
private int max;
/** Current context. */
private Context context = new Context(false);
/** Creates new instance of the check. */
public BooleanExpressionComplexityCheck() {
max = DEFAULT_MAX;
}
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.EXPR,
TokenTypes.LAND,
TokenTypes.BAND,
TokenTypes.LOR,
TokenTypes.BOR,
TokenTypes.BXOR,
};
}
@Override
public int[] getRequiredTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.EXPR,
};
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.EXPR,
TokenTypes.LAND,
TokenTypes.BAND,
TokenTypes.LOR,
TokenTypes.BOR,
TokenTypes.BXOR,
};
}
/**
* Getter for maximum allowed complexity.
* @return value of maximum allowed complexity.
*/
public int getMax() {
return max;
}
/**
* Setter for maximum allowed complexity.
* @param max new maximum allowed complexity.
*/
public void setMax(int max) {
this.max = max;
}
@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.CTOR_DEF:
case TokenTypes.METHOD_DEF:
visitMethodDef(ast);
break;
case TokenTypes.EXPR:
visitExpr();
break;
case TokenTypes.BOR:
if (!isPipeOperator(ast) && !isPassedInParameter(ast)) {
context.visitBooleanOperator();
}
break;
case TokenTypes.BAND:
case TokenTypes.BXOR:
if (!isPassedInParameter(ast)) {
context.visitBooleanOperator();
}
break;
case TokenTypes.LAND:
case TokenTypes.LOR:
context.visitBooleanOperator();
break;
default:
throw new IllegalArgumentException("Unknown type: " + ast);
}
}
/**
* Checks if logical operator is part of constructor or method call.
* @param logicalOperator logical operator
* @return true if logical operator is part of constructor or method call
*/
private static boolean isPassedInParameter(DetailAST logicalOperator) {
return logicalOperator.getParent().getType() == TokenTypes.EXPR
&& logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST;
}
/**
* Checks if {@link TokenTypes#BOR binary OR} is applied to exceptions
* in
* <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20">
* multi-catch</a> (pipe-syntax).
* @param binaryOr {@link TokenTypes#BOR binary or}
* @return true if binary or is applied to exceptions in multi-catch.
*/
private static boolean isPipeOperator(DetailAST binaryOr) {
return binaryOr.getParent().getType() == TokenTypes.TYPE;
}
@Override
public void leaveToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.CTOR_DEF:
case TokenTypes.METHOD_DEF:
leaveMethodDef();
break;
case TokenTypes.EXPR:
leaveExpr(ast);
break;
default:
// Do nothing
}
}
/**
* Creates new context for a given method.
* @param ast a method we start to check.
*/
private void visitMethodDef(DetailAST ast) {
contextStack.push(context);
final boolean check = !CheckUtils.isEqualsMethod(ast);
context = new Context(check);
}
/** Removes old context. */
private void leaveMethodDef() {
context = contextStack.pop();
}
/** Creates and pushes new context. */
private void visitExpr() {
contextStack.push(context);
context = new Context(context.isChecking());
}
/**
* Restores previous context.
* @param ast expression we leave.
*/
private void leaveExpr(DetailAST ast) {
context.checkCount(ast);
context = contextStack.pop();
}
/**
* Represents context (method/expression) in which we check complexity.
*
* @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
* @author o_sukhodolsky
*/
private class Context {
/**
* Should we perform check in current context or not.
* Usually false if we are inside equals() method.
*/
private final boolean checking;
/** Count of boolean operators. */
private int count;
/**
* Creates new instance.
* @param checking should we check in current context or not.
*/
Context(boolean checking) {
this.checking = checking;
count = 0;
}
/**
* Getter for checking property.
* @return should we check in current context or not.
*/
public boolean isChecking() {
return checking;
}
/** Increases operator counter. */
public void visitBooleanOperator() {
++count;
}
/**
* Checks if we violates maximum allowed complexity.
* @param ast a node we check now.
*/
public void checkCount(DetailAST ast) {
if (checking && count > getMax()) {
final DetailAST parentAST = ast.getParent();
log(parentAST.getLineNo(), parentAST.getColumnNo(),
MSG_KEY, count, getMax());
}
}
}
}