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.whitespace;
021
022import java.util.Arrays;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
027
028/**
029 * <p>Checks the padding of parentheses; that is whether a space is required
030 * after a left parenthesis and before a right parenthesis, or such spaces are
031 * forbidden. No check occurs at the right parenthesis after an empty for
032 * iterator, at the left parenthesis before an empty for initialization, or at
033 * the right parenthesis of a try-with-resources resource specification where
034 * the last resource variable has a trailing semi-colon.
035 * Use Check {@link EmptyForIteratorPadCheck EmptyForIteratorPad} to validate
036 * empty for iterators and {@link EmptyForInitializerPadCheck EmptyForInitializerPad}
037 * to validate empty for initializers. Typecasts are also not checked, as there is
038 * {@link TypecastParenPadCheck TypecastParenPad} to validate them.
039 * </p>
040 * <p>
041 * The policy to verify is specified using the {@link PadOption} class and
042 * defaults to {@link PadOption#NOSPACE}.
043 * </p>
044 * <p> By default the check will check parentheses that occur with the following
045 * tokens:
046 *  {@link TokenTypes#ANNOTATION ANNOTATION},
047 *  {@link TokenTypes#ANNOTATION_FIELD_DEF ANNOTATION_FIELD_DEF},
048 *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
049 *  {@link TokenTypes#CTOR_CALL CTOR_CALL},
050 *  {@link TokenTypes#DOT DOT},
051 *  {@link TokenTypes#ENUM_CONSTANT_DEF ENUM_CONSTANT_DEF},
052 *  {@link TokenTypes#EXPR EXPR},
053 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
054 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
055 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
056 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
057 *  {@link TokenTypes#LITERAL_NEW LITERAL_NEW},
058 *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},
059 *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},
060 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
061 *  {@link TokenTypes#METHOD_CALL METHOD_CALL},
062 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
063 *  {@link TokenTypes#RESOURCE_SPECIFICATION RESOURCE_SPECIFICATION},
064 *  {@link TokenTypes#SUPER_CTOR_CALL SUPER_CTOR_CALL},
065 *  {@link TokenTypes#QUESTION QUESTION},
066 *  {@link TokenTypes#LAMBDA LAMBDA},
067 * </p>
068 * <p>
069 * An example of how to configure the check is:
070 * </p>
071 * <pre>
072 * &lt;module name="ParenPad"/&gt;
073 * </pre>
074 * <p>
075 * An example of how to configure the check to require spaces for the
076 * parentheses of constructor, method, and super constructor invocations is:
077 * </p>
078 * <pre>
079 * &lt;module name="ParenPad"&gt;
080 *     &lt;property name="tokens"
081 *               value="CTOR_CALL, METHOD_CALL, SUPER_CTOR_CALL"/&gt;
082 *     &lt;property name="option" value="space"/&gt;
083 * &lt;/module&gt;
084 * </pre>
085 * @author Oliver Burn
086 * @author Vladislav Lisetskiy
087 */
088public class ParenPadCheck extends AbstractParenPadCheck {
089
090    /**
091     * The array of Acceptable Tokens.
092     */
093    private final int[] acceptableTokens;
094
095    /**
096     * Initializes and sorts acceptableTokens to make binary search over it possible.
097     */
098    public ParenPadCheck() {
099        acceptableTokens = makeAcceptableTokens();
100        Arrays.sort(acceptableTokens);
101    }
102
103    @Override
104    public int[] getDefaultTokens() {
105        return makeAcceptableTokens();
106    }
107
108    @Override
109    public int[] getAcceptableTokens() {
110        return makeAcceptableTokens();
111    }
112
113    @Override
114    public int[] getRequiredTokens() {
115        return CommonUtils.EMPTY_INT_ARRAY;
116    }
117
118    @Override
119    public void visitToken(DetailAST ast) {
120        switch (ast.getType()) {
121            case TokenTypes.METHOD_CALL:
122                processLeft(ast);
123                processRight(ast.findFirstToken(TokenTypes.RPAREN));
124                processExpression(ast);
125                break;
126            case TokenTypes.DOT:
127            case TokenTypes.EXPR:
128            case TokenTypes.QUESTION:
129                processExpression(ast);
130                break;
131            case TokenTypes.LITERAL_FOR:
132                visitLiteralFor(ast);
133                break;
134            case TokenTypes.ANNOTATION:
135            case TokenTypes.ENUM_CONSTANT_DEF:
136            case TokenTypes.LITERAL_NEW:
137            case TokenTypes.LITERAL_SYNCHRONIZED:
138            case TokenTypes.LAMBDA:
139                visitTokenWithOptionalParentheses(ast);
140                break;
141            case TokenTypes.RESOURCE_SPECIFICATION:
142                visitResourceSpecification(ast);
143                break;
144            default:
145                processLeft(ast.findFirstToken(TokenTypes.LPAREN));
146                processRight(ast.findFirstToken(TokenTypes.RPAREN));
147        }
148    }
149
150    /**
151     * Checks parens in token which may not contain parens, e.g.
152     * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
153     * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
154     * {@link TokenTypes#LAMBDA}.
155     * @param ast the token to check.
156     */
157    private void visitTokenWithOptionalParentheses(DetailAST ast) {
158        final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
159        if (parenAst != null) {
160            processLeft(parenAst);
161            processRight(ast.findFirstToken(TokenTypes.RPAREN));
162        }
163    }
164
165    /**
166     * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
167     * @param ast the token to check.
168     */
169    private void visitResourceSpecification(DetailAST ast) {
170        processLeft(ast.findFirstToken(TokenTypes.LPAREN));
171        final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
172        if (!hasPrecedingSemiColon(rparen)) {
173            processRight(rparen);
174        }
175    }
176
177    /**
178     * Checks that a token is preceded by a semi-colon.
179     * @param ast the token to check
180     * @return whether a token is preceded by a semi-colon
181     */
182    private static boolean hasPrecedingSemiColon(DetailAST ast) {
183        return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
184    }
185
186    /**
187     * Checks parens in {@link TokenTypes#LITERAL_FOR}.
188     * @param ast the token to check.
189     */
190    private void visitLiteralFor(DetailAST ast) {
191        final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
192        if (!isPrecedingEmptyForInit(lparen)) {
193            processLeft(lparen);
194        }
195        final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
196        if (!isFollowsEmptyForIterator(rparen)) {
197            processRight(rparen);
198        }
199    }
200
201    /**
202     * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
203     * and {@link TokenTypes#METHOD_CALL}.
204     * @param ast the token to check.
205     */
206    private void processExpression(DetailAST ast) {
207        if (ast.branchContains(TokenTypes.LPAREN)) {
208            DetailAST childAst = ast.getFirstChild();
209            while (childAst != null) {
210                if (childAst.getType() == TokenTypes.LPAREN) {
211                    processLeft(childAst);
212                    processExpression(childAst);
213                }
214                else if (childAst.getType() == TokenTypes.RPAREN && !isInTypecast(childAst)) {
215                    processRight(childAst);
216                }
217                else if (!isAcceptableToken(childAst)) {
218                    //Traverse all subtree tokens which will never be configured
219                    //to be launched in visitToken()
220                    processExpression(childAst);
221                }
222                childAst = childAst.getNextSibling();
223            }
224        }
225    }
226
227    /**
228     * Checks whether AcceptableTokens contains the given ast.
229     * @param ast the token to check.
230     * @return true if the ast is in AcceptableTokens.
231     */
232    private boolean isAcceptableToken(DetailAST ast) {
233        boolean result = false;
234        if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) {
235            result = true;
236        }
237        return result;
238    }
239
240    /**
241     * Returns array of acceptable tokens.
242     * @return acceptableTokens.
243     */
244    private static int[] makeAcceptableTokens() {
245        return new int[] {TokenTypes.ANNOTATION,
246            TokenTypes.ANNOTATION_FIELD_DEF,
247            TokenTypes.CTOR_CALL,
248            TokenTypes.CTOR_DEF,
249            TokenTypes.DOT,
250            TokenTypes.ENUM_CONSTANT_DEF,
251            TokenTypes.EXPR,
252            TokenTypes.LITERAL_CATCH,
253            TokenTypes.LITERAL_DO,
254            TokenTypes.LITERAL_FOR,
255            TokenTypes.LITERAL_IF,
256            TokenTypes.LITERAL_NEW,
257            TokenTypes.LITERAL_SWITCH,
258            TokenTypes.LITERAL_SYNCHRONIZED,
259            TokenTypes.LITERAL_WHILE,
260            TokenTypes.METHOD_CALL,
261            TokenTypes.METHOD_DEF,
262            TokenTypes.QUESTION,
263            TokenTypes.RESOURCE_SPECIFICATION,
264            TokenTypes.SUPER_CTOR_CALL,
265            TokenTypes.LAMBDA,
266        };
267    }
268
269    /**
270     * Checks whether {@link TokenTypes#RPAREN} is a closing paren
271     * of a {@link TokenTypes#TYPECAST}.
272     * @param ast of a {@link TokenTypes#RPAREN} to check.
273     * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
274     */
275    private static boolean isInTypecast(DetailAST ast) {
276        boolean result = false;
277        if (ast.getParent().getType() == TokenTypes.TYPECAST) {
278            final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
279            if (firstRparen.getLineNo() == ast.getLineNo()
280                    && firstRparen.getColumnNo() == ast.getColumnNo()) {
281                result = true;
282            }
283        }
284        return result;
285    }
286
287    /**
288     * Checks that a token follows an empty for iterator.
289     * @param ast the token to check
290     * @return whether a token follows an empty for iterator
291     */
292    private static boolean isFollowsEmptyForIterator(DetailAST ast) {
293        boolean result = false;
294        final DetailAST parent = ast.getParent();
295        //Only traditional for statements are examined, not for-each statements
296        if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
297            final DetailAST forIterator =
298                parent.findFirstToken(TokenTypes.FOR_ITERATOR);
299            result = forIterator.getChildCount() == 0;
300        }
301        return result;
302    }
303
304    /**
305     * Checks that a token precedes an empty for initializer.
306     * @param ast the token to check
307     * @return whether a token precedes an empty for initializer
308     */
309    private static boolean isPrecedingEmptyForInit(DetailAST ast) {
310        boolean result = false;
311        final DetailAST parent = ast.getParent();
312        //Only traditional for statements are examined, not for-each statements
313        if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
314            final DetailAST forIterator =
315                    parent.findFirstToken(TokenTypes.FOR_INIT);
316            result = forIterator.getChildCount() == 0;
317        }
318        return result;
319    }
320}