001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import antlr.collections.AST;
023
024import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * <p>
030 * Checks for overly complicated boolean return statements.
031 * Idea shamelessly stolen from the equivalent PMD rule (pmd.sourceforge.net).
032 * </p>
033 * <p>
034 * An example of how to configure the check is:
035 * </p>
036 * <pre>
037 * &lt;module name="SimplifyBooleanReturn"/&gt;
038 * </pre>
039 * @author Lars Kühne
040 */
041public class SimplifyBooleanReturnCheck
042    extends AbstractCheck {
043
044    /**
045     * A key is pointing to the warning message text in "messages.properties"
046     * file.
047     */
048    public static final String MSG_KEY = "simplify.boolReturn";
049
050    @Override
051    public int[] getAcceptableTokens() {
052        return new int[] {TokenTypes.LITERAL_IF};
053    }
054
055    @Override
056    public int[] getDefaultTokens() {
057        return getAcceptableTokens();
058    }
059
060    @Override
061    public int[] getRequiredTokens() {
062        return getAcceptableTokens();
063    }
064
065    @Override
066    public void visitToken(DetailAST ast) {
067        // LITERAL_IF has the following four or five children:
068        // '('
069        // condition
070        // ')'
071        // thenStatement
072        // [ LITERAL_ELSE (with the elseStatement as a child) ]
073
074        // don't bother if this is not if then else
075        final AST elseLiteral =
076            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
077        if (elseLiteral != null) {
078            final AST elseStatement = elseLiteral.getFirstChild();
079
080            // skip '(' and ')'
081            final AST condition = ast.getFirstChild().getNextSibling();
082            final AST thenStatement = condition.getNextSibling().getNextSibling();
083
084            if (canReturnOnlyBooleanLiteral(thenStatement)
085                && canReturnOnlyBooleanLiteral(elseStatement)) {
086                log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY);
087            }
088        }
089    }
090
091    /**
092     * Returns if an AST is a return statement with a boolean literal
093     * or a compound statement that contains only such a return statement.
094     *
095     * <p>Returns {@code true} iff ast represents
096     * <br/>
097     * <pre>
098     * return true/false;
099     * </pre>
100     * or
101     * <br/>
102     * <pre>
103     * {
104     *   return true/false;
105     * }
106     * </pre>
107     *
108     * @param ast the syntax tree to check
109     * @return if ast is a return statement with a boolean literal.
110     */
111    private static boolean canReturnOnlyBooleanLiteral(AST ast) {
112        if (isBooleanLiteralReturnStatement(ast)) {
113            return true;
114        }
115
116        final AST firstStatement = ast.getFirstChild();
117        return isBooleanLiteralReturnStatement(firstStatement);
118    }
119
120    /**
121     * Returns if an AST is a return statement with a boolean literal.
122     *
123     * <p>Returns {@code true} iff ast represents
124     * <br/>
125     * <pre>
126     * return true/false;
127     * </pre>
128     *
129     * @param ast the syntax tree to check
130     * @return if ast is a return statement with a boolean literal.
131     */
132    private static boolean isBooleanLiteralReturnStatement(AST ast) {
133        boolean booleanReturnStatement = false;
134
135        if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
136            final AST expr = ast.getFirstChild();
137
138            if (expr.getType() != TokenTypes.SEMI) {
139                final AST value = expr.getFirstChild();
140                booleanReturnStatement = isBooleanLiteralType(value.getType());
141            }
142        }
143        return booleanReturnStatement;
144    }
145
146    /**
147     * Checks if a token type is a literal true or false.
148     * @param tokenType the TokenType
149     * @return true iff tokenType is LITERAL_TRUE or LITERAL_FALSE
150     */
151    private static boolean isBooleanLiteralType(final int tokenType) {
152        final boolean isTrue = tokenType == TokenTypes.LITERAL_TRUE;
153        final boolean isFalse = tokenType == TokenTypes.LITERAL_FALSE;
154        return isTrue || isFalse;
155    }
156}