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 org.apache.commons.lang3.ArrayUtils;
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 if unnecessary parentheses are used in a statement or expression.
031 * The check will flag the following with warnings:
032 * </p>
033 * <pre>
034 *     return (x);          // parens around identifier
035 *     return (x + 1);      // parens around return value
036 *     int x = (y / 2 + 1); // parens around assignment rhs
037 *     for (int i = (0); i &lt; 10; i++) {  // parens around literal
038 *     t -= (z + 1);        // parens around assignment rhs</pre>
039 * <p>
040 * The check is not "type aware", that is to say, it can't tell if parentheses
041 * are unnecessary based on the types in an expression.  It also doesn't know
042 * about operator precedence and associativity; therefore it won't catch
043 * something like
044 * </p>
045 * <pre>
046 *     int x = (a + b) + c;</pre>
047 * <p>
048 * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are
049 * all {@code int} variables, the parentheses around {@code a + b}
050 * are not needed.
051 * </p>
052 *
053 * @author Eric Roe
054 */
055public class UnnecessaryParenthesesCheck extends Check {
056
057    /**
058     * A key is pointing to the warning message text in "messages.properties"
059     * file.
060     */
061    public static final String MSG_IDENT = "unnecessary.paren.ident";
062
063    /**
064     * A key is pointing to the warning message text in "messages.properties"
065     * file.
066     */
067    public static final String MSG_ASSIGN = "unnecessary.paren.assign";
068
069    /**
070     * A key is pointing to the warning message text in "messages.properties"
071     * file.
072     */
073    public static final String MSG_EXPR = "unnecessary.paren.expr";
074
075    /**
076     * A key is pointing to the warning message text in "messages.properties"
077     * file.
078     */
079    public static final String MSG_LITERAL = "unnecessary.paren.literal";
080
081    /**
082     * A key is pointing to the warning message text in "messages.properties"
083     * file.
084     */
085    public static final String MSG_STRING = "unnecessary.paren.string";
086
087    /**
088     * A key is pointing to the warning message text in "messages.properties"
089     * file.
090     */
091    public static final String MSG_RETURN = "unnecessary.paren.return";
092
093    /** The maximum string length before we chop the string. */
094    private static final int MAX_QUOTED_LENGTH = 25;
095
096    /** Token types for literals. */
097    private static final int[] LITERALS = {
098        TokenTypes.NUM_DOUBLE,
099        TokenTypes.NUM_FLOAT,
100        TokenTypes.NUM_INT,
101        TokenTypes.NUM_LONG,
102        TokenTypes.STRING_LITERAL,
103        TokenTypes.LITERAL_NULL,
104        TokenTypes.LITERAL_FALSE,
105        TokenTypes.LITERAL_TRUE,
106    };
107
108    /** Token types for assignment operations. */
109    private static final int[] ASSIGNMENTS = {
110        TokenTypes.ASSIGN,
111        TokenTypes.BAND_ASSIGN,
112        TokenTypes.BOR_ASSIGN,
113        TokenTypes.BSR_ASSIGN,
114        TokenTypes.BXOR_ASSIGN,
115        TokenTypes.DIV_ASSIGN,
116        TokenTypes.MINUS_ASSIGN,
117        TokenTypes.MOD_ASSIGN,
118        TokenTypes.PLUS_ASSIGN,
119        TokenTypes.SL_ASSIGN,
120        TokenTypes.SR_ASSIGN,
121        TokenTypes.STAR_ASSIGN,
122    };
123
124    /**
125     * Used to test if logging a warning in a parent node may be skipped
126     * because a warning was already logged on an immediate child node.
127     */
128    private DetailAST parentToSkip;
129    /** Depth of nested assignments.  Normally this will be 0 or 1. */
130    private int assignDepth;
131
132    @Override
133    public int[] getDefaultTokens() {
134        return new int[] {
135            TokenTypes.EXPR,
136            TokenTypes.IDENT,
137            TokenTypes.NUM_DOUBLE,
138            TokenTypes.NUM_FLOAT,
139            TokenTypes.NUM_INT,
140            TokenTypes.NUM_LONG,
141            TokenTypes.STRING_LITERAL,
142            TokenTypes.LITERAL_NULL,
143            TokenTypes.LITERAL_FALSE,
144            TokenTypes.LITERAL_TRUE,
145            TokenTypes.ASSIGN,
146            TokenTypes.BAND_ASSIGN,
147            TokenTypes.BOR_ASSIGN,
148            TokenTypes.BSR_ASSIGN,
149            TokenTypes.BXOR_ASSIGN,
150            TokenTypes.DIV_ASSIGN,
151            TokenTypes.MINUS_ASSIGN,
152            TokenTypes.MOD_ASSIGN,
153            TokenTypes.PLUS_ASSIGN,
154            TokenTypes.SL_ASSIGN,
155            TokenTypes.SR_ASSIGN,
156            TokenTypes.STAR_ASSIGN,
157        };
158    }
159
160    @Override
161    public int[] getAcceptableTokens() {
162        return new int[] {
163            TokenTypes.EXPR,
164            TokenTypes.IDENT,
165            TokenTypes.NUM_DOUBLE,
166            TokenTypes.NUM_FLOAT,
167            TokenTypes.NUM_INT,
168            TokenTypes.NUM_LONG,
169            TokenTypes.STRING_LITERAL,
170            TokenTypes.LITERAL_NULL,
171            TokenTypes.LITERAL_FALSE,
172            TokenTypes.LITERAL_TRUE,
173            TokenTypes.ASSIGN,
174            TokenTypes.BAND_ASSIGN,
175            TokenTypes.BOR_ASSIGN,
176            TokenTypes.BSR_ASSIGN,
177            TokenTypes.BXOR_ASSIGN,
178            TokenTypes.DIV_ASSIGN,
179            TokenTypes.MINUS_ASSIGN,
180            TokenTypes.MOD_ASSIGN,
181            TokenTypes.PLUS_ASSIGN,
182            TokenTypes.SL_ASSIGN,
183            TokenTypes.SR_ASSIGN,
184            TokenTypes.STAR_ASSIGN,
185        };
186    }
187
188    @Override
189    public int[] getRequiredTokens() {
190        // Check can work with any of acceptable tokens
191        return ArrayUtils.EMPTY_INT_ARRAY;
192    }
193
194    @Override
195    public void visitToken(DetailAST ast) {
196        final int type = ast.getType();
197        final DetailAST parent = ast.getParent();
198
199        if (type != TokenTypes.ASSIGN
200            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
201
202            final boolean surrounded = isSurrounded(ast);
203            // An identifier surrounded by parentheses.
204            if (surrounded && type == TokenTypes.IDENT) {
205                parentToSkip = ast.getParent();
206                log(ast, MSG_IDENT, ast.getText());
207            }
208            // A literal (numeric or string) surrounded by parentheses.
209            else if (surrounded && isInTokenList(type, LITERALS)) {
210                parentToSkip = ast.getParent();
211                if (type == TokenTypes.STRING_LITERAL) {
212                    log(ast, MSG_STRING,
213                        chopString(ast.getText()));
214                }
215                else {
216                    log(ast, MSG_LITERAL, ast.getText());
217                }
218            }
219            // The rhs of an assignment surrounded by parentheses.
220            else if (isInTokenList(type, ASSIGNMENTS)) {
221                assignDepth++;
222                final DetailAST last = ast.getLastChild();
223                if (last.getType() == TokenTypes.RPAREN) {
224                    log(ast, MSG_ASSIGN);
225                }
226            }
227        }
228    }
229
230    @Override
231    public void leaveToken(DetailAST ast) {
232        final int type = ast.getType();
233        final DetailAST parent = ast.getParent();
234
235        if (type == TokenTypes.ASSIGN
236            && parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
237            // shouldn't process assign in annotation pairs
238            return;
239        }
240
241        // An expression is surrounded by parentheses.
242        if (type == TokenTypes.EXPR) {
243
244            // If 'parentToSkip' == 'ast', then we've already logged a
245            // warning about an immediate child node in visitToken, so we don't
246            // need to log another one here.
247
248            if (parentToSkip != ast && isExprSurrounded(ast)) {
249                if (assignDepth >= 1) {
250                    log(ast, MSG_ASSIGN);
251                }
252                else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) {
253                    log(ast, MSG_RETURN);
254                }
255                else {
256                    log(ast, MSG_EXPR);
257                }
258            }
259
260            parentToSkip = null;
261        }
262        else if (isInTokenList(type, ASSIGNMENTS)) {
263            assignDepth--;
264        }
265
266        super.leaveToken(ast);
267    }
268
269    /**
270     * Tests if the given {@code DetailAST} is surrounded by parentheses.
271     * In short, does {@code ast} have a previous sibling whose type is
272     * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code
273     * TokenTypes.RPAREN}.
274     * @param ast the {@code DetailAST} to check if it is surrounded by
275     *        parentheses.
276     * @return {@code true} if {@code ast} is surrounded by
277     *         parentheses.
278     */
279    private static boolean isSurrounded(DetailAST ast) {
280        // if previous sibling is left parenthesis,
281        // next sibling can't be other than right parenthesis
282        final DetailAST prev = ast.getPreviousSibling();
283        return prev != null && prev.getType() == TokenTypes.LPAREN;
284    }
285
286    /**
287     * Tests if the given expression node is surrounded by parentheses.
288     * @param ast a {@code DetailAST} whose type is
289     *        {@code TokenTypes.EXPR}.
290     * @return {@code true} if the expression is surrounded by
291     *         parentheses.
292     */
293    private static boolean isExprSurrounded(DetailAST ast) {
294        return ast.getFirstChild().getType() == TokenTypes.LPAREN;
295    }
296
297    /**
298     * Check if the given token type can be found in an array of token types.
299     * @param type the token type.
300     * @param tokens an array of token types to search.
301     * @return {@code true} if {@code type} was found in {@code
302     *         tokens}.
303     */
304    private static boolean isInTokenList(int type, int... tokens) {
305        // NOTE: Given the small size of the two arrays searched, I'm not sure
306        //       it's worth bothering with doing a binary search or using a
307        //       HashMap to do the searches.
308
309        boolean found = false;
310        for (int i = 0; i < tokens.length && !found; i++) {
311            found = tokens[i] == type;
312        }
313        return found;
314    }
315
316    /**
317     * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH}
318     * plus an ellipsis (...) if the length of the string exceeds {@code
319     * MAX_QUOTED_LENGTH}.
320     * @param value the string to potentially chop.
321     * @return the chopped string if {@code string} is longer than
322     *         {@code MAX_QUOTED_LENGTH}; otherwise {@code string}.
323     */
324    private static String chopString(String value) {
325        if (value.length() > MAX_QUOTED_LENGTH) {
326            return value.substring(0, MAX_QUOTED_LENGTH) + "...\"";
327        }
328        return value;
329    }
330}