001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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;
023import com.puppycrawl.tools.checkstyle.StatelessCheck;
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 */
041@StatelessCheck
042public class SimplifyBooleanReturnCheck
043    extends AbstractCheck {
044
045    /**
046     * A key is pointing to the warning message text in "messages.properties"
047     * file.
048     */
049    public static final String MSG_KEY = "simplify.boolReturn";
050
051    @Override
052    public int[] getAcceptableTokens() {
053        return getRequiredTokens();
054    }
055
056    @Override
057    public int[] getDefaultTokens() {
058        return getRequiredTokens();
059    }
060
061    @Override
062    public int[] getRequiredTokens() {
063        return new int[] {TokenTypes.LITERAL_IF};
064    }
065
066    @Override
067    public void visitToken(DetailAST ast) {
068        // LITERAL_IF has the following four or five children:
069        // '('
070        // condition
071        // ')'
072        // thenStatement
073        // [ LITERAL_ELSE (with the elseStatement as a child) ]
074
075        // don't bother if this is not if then else
076        final AST elseLiteral =
077            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
078        if (elseLiteral != null) {
079            final AST elseStatement = elseLiteral.getFirstChild();
080
081            // skip '(' and ')'
082            final AST condition = ast.getFirstChild().getNextSibling();
083            final AST thenStatement = condition.getNextSibling().getNextSibling();
084
085            if (canReturnOnlyBooleanLiteral(thenStatement)
086                && canReturnOnlyBooleanLiteral(elseStatement)) {
087                log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY);
088            }
089        }
090    }
091
092    /**
093     * Returns if an AST is a return statement with a boolean literal
094     * or a compound statement that contains only such a return statement.
095     *
096     * <p>Returns {@code true} iff ast represents
097     * <br/>
098     * <pre>
099     * return true/false;
100     * </pre>
101     * or
102     * <br/>
103     * <pre>
104     * {
105     *   return true/false;
106     * }
107     * </pre>
108     *
109     * @param ast the syntax tree to check
110     * @return if ast is a return statement with a boolean literal.
111     */
112    private static boolean canReturnOnlyBooleanLiteral(AST ast) {
113        boolean result = true;
114        if (!isBooleanLiteralReturnStatement(ast)) {
115            final AST firstStatement = ast.getFirstChild();
116            result = isBooleanLiteralReturnStatement(firstStatement);
117        }
118        return result;
119    }
120
121    /**
122     * Returns if an AST is a return statement with a boolean literal.
123     *
124     * <p>Returns {@code true} iff ast represents
125     * <br/>
126     * <pre>
127     * return true/false;
128     * </pre>
129     *
130     * @param ast the syntax tree to check
131     * @return if ast is a return statement with a boolean literal.
132     */
133    private static boolean isBooleanLiteralReturnStatement(AST ast) {
134        boolean booleanReturnStatement = false;
135
136        if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
137            final AST expr = ast.getFirstChild();
138
139            if (expr.getType() != TokenTypes.SEMI) {
140                final AST value = expr.getFirstChild();
141                booleanReturnStatement = isBooleanLiteralType(value.getType());
142            }
143        }
144        return booleanReturnStatement;
145    }
146
147    /**
148     * Checks if a token type is a literal true or false.
149     * @param tokenType the TokenType
150     * @return true iff tokenType is LITERAL_TRUE or LITERAL_FALSE
151     */
152    private static boolean isBooleanLiteralType(final int tokenType) {
153        final boolean isTrue = tokenType == TokenTypes.LITERAL_TRUE;
154        final boolean isFalse = tokenType == TokenTypes.LITERAL_FALSE;
155        return isTrue || isFalse;
156    }
157}