001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.Check;
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 Check {
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            return;
079        }
080        final AST elseStatement = elseLiteral.getFirstChild();
081
082        // skip '(' and ')'
083        final AST condition = ast.getFirstChild().getNextSibling();
084        final AST thenStatement = condition.getNextSibling().getNextSibling();
085
086        if (canReturnOnlyBooleanLiteral(thenStatement)
087            && canReturnOnlyBooleanLiteral(elseStatement)) {
088            log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY);
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        if (isBooleanLiteralReturnStatement(ast)) {
114            return true;
115        }
116
117        final AST firstStatement = ast.getFirstChild();
118        return isBooleanLiteralReturnStatement(firstStatement);
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}