001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026
027/**
028 * <p>
029 * Checks for over-complicated boolean return statements.
030 * For example the following code
031 * </p>
032 * <pre>
033 * if (valid())
034 *   return false;
035 * else
036 *   return true;
037 * </pre>
038 * <p>
039 * could be written as
040 * </p>
041 * <pre>
042 * return !valid();
043 * </pre>
044 * <p>
045 * The idea for this Check has been shamelessly stolen from the equivalent
046 * <a href="https://pmd.github.io/">PMD</a> rule.
047 * </p>
048 * <p>
049 * To configure the check:
050 * </p>
051 * <pre>
052 * &lt;module name=&quot;SimplifyBooleanReturn&quot;/&gt;
053 * </pre>
054 * <p>Example:</p>
055 * <pre>
056 * public class Test {
057 *
058 *  private boolean cond;
059 *  private Foo a;
060 *  private Foo b;
061 *
062 *  public boolean check1() {
063 *   if (cond) { // violation, can be simplified
064 *     return true;
065 *   }
066 *   else {
067 *     return false;
068 *   }
069 *  }
070 *
071 *  // Ok, simplified version of check1()
072 *  public boolean check2() {
073 *   return cond;
074 *  }
075 *
076 *  // violations, can be simplified
077 *  public boolean check3() {
078 *   if (cond == true) { // can be simplified to "if (cond)"
079 *     return false;
080 *   }
081 *   else {
082 *     return true; // can be simplified to "return !cond"
083 *   }
084 *  }
085 *
086 *  // Ok, can be simplified but doesn't return a Boolean
087 *  public Foo choose1() {
088 *   if (cond) {
089 *     return a;
090 *   }
091 *   else {
092 *     return b;
093 *   }
094 *  }
095 *
096 *  // Ok, simplified version of choose1()
097 *  public Foo choose2() {
098 *   return cond ? a: b;
099 *  }
100 *
101 * }
102 * </pre>
103 * <p>
104 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
105 * </p>
106 * <p>
107 * Violation Message Keys:
108 * </p>
109 * <ul>
110 * <li>
111 * {@code simplify.boolReturn}
112 * </li>
113 * </ul>
114 *
115 * @since 3.0
116 */
117@StatelessCheck
118public class SimplifyBooleanReturnCheck
119    extends AbstractCheck {
120
121    /**
122     * A key is pointing to the warning message text in "messages.properties"
123     * file.
124     */
125    public static final String MSG_KEY = "simplify.boolReturn";
126
127    @Override
128    public int[] getAcceptableTokens() {
129        return getRequiredTokens();
130    }
131
132    @Override
133    public int[] getDefaultTokens() {
134        return getRequiredTokens();
135    }
136
137    @Override
138    public int[] getRequiredTokens() {
139        return new int[] {TokenTypes.LITERAL_IF};
140    }
141
142    @Override
143    public void visitToken(DetailAST ast) {
144        // LITERAL_IF has the following four or five children:
145        // '('
146        // condition
147        // ')'
148        // thenStatement
149        // [ LITERAL_ELSE (with the elseStatement as a child) ]
150
151        // don't bother if this is not if then else
152        final DetailAST elseLiteral =
153            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
154        if (elseLiteral != null) {
155            final DetailAST elseStatement = elseLiteral.getFirstChild();
156
157            // skip '(' and ')'
158            final DetailAST condition = ast.getFirstChild().getNextSibling();
159            final DetailAST thenStatement = condition.getNextSibling().getNextSibling();
160
161            if (canReturnOnlyBooleanLiteral(thenStatement)
162                && canReturnOnlyBooleanLiteral(elseStatement)) {
163                log(ast, MSG_KEY);
164            }
165        }
166    }
167
168    /**
169     * Returns if an AST is a return statement with a boolean literal
170     * or a compound statement that contains only such a return statement.
171     *
172     * <p>Returns {@code true} iff ast represents
173     * <br/>
174     * <pre>
175     * return true/false;
176     * </pre>
177     * or
178     * <br/>
179     * <pre>
180     * {
181     *   return true/false;
182     * }
183     * </pre>
184     *
185     * @param ast the syntax tree to check
186     * @return if ast is a return statement with a boolean literal.
187     */
188    private static boolean canReturnOnlyBooleanLiteral(DetailAST ast) {
189        boolean result = true;
190        if (!isBooleanLiteralReturnStatement(ast)) {
191            final DetailAST firstStatement = ast.getFirstChild();
192            result = isBooleanLiteralReturnStatement(firstStatement);
193        }
194        return result;
195    }
196
197    /**
198     * Returns if an AST is a return statement with a boolean literal.
199     *
200     * <p>Returns {@code true} iff ast represents
201     * <br/>
202     * <pre>
203     * return true/false;
204     * </pre>
205     *
206     * @param ast the syntax tree to check
207     * @return if ast is a return statement with a boolean literal.
208     */
209    private static boolean isBooleanLiteralReturnStatement(DetailAST ast) {
210        boolean booleanReturnStatement = false;
211
212        if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
213            final DetailAST expr = ast.getFirstChild();
214
215            if (expr.getType() != TokenTypes.SEMI) {
216                final DetailAST value = expr.getFirstChild();
217                booleanReturnStatement = isBooleanLiteralType(value.getType());
218            }
219        }
220        return booleanReturnStatement;
221    }
222
223    /**
224     * Checks if a token type is a literal true or false.
225     *
226     * @param tokenType the TokenType
227     * @return true iff tokenType is LITERAL_TRUE or LITERAL_FALSE
228     */
229    private static boolean isBooleanLiteralType(final int tokenType) {
230        final boolean isTrue = tokenType == TokenTypes.LITERAL_TRUE;
231        final boolean isFalse = tokenType == TokenTypes.LITERAL_FALSE;
232        return isTrue || isFalse;
233    }
234
235}