001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.BitSet;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
027import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
028
029/**
030 * <div>
031 * Checks the policy on the padding of parentheses; that is whether a space is required
032 * after a left parenthesis and before a right parenthesis, or such spaces are
033 * forbidden. No check occurs at the right parenthesis after an empty for
034 * iterator, at the left parenthesis before an empty for initialization, or at
035 * the right parenthesis of a try-with-resources resource specification where
036 * the last resource variable has a trailing semicolon.
037 * Use Check
038 * <a href="https://checkstyle.org/checks/whitespace/emptyforiteratorpad.html">
039 * EmptyForIteratorPad</a> to validate empty for iterators and
040 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html">
041 * EmptyForInitializerPad</a> to validate empty for initializers.
042 * Typecasts are also not checked, as there is
043 * <a href="https://checkstyle.org/checks/whitespace/typecastparenpad.html">
044 * TypecastParenPad</a> to validate them.
045 * </div>
046 *
047 * @since 3.0
048 */
049public class ParenPadCheck extends AbstractParenPadCheck {
050
051    /**
052     * A key is pointing to the warning message text in "messages.properties"
053     * file.
054     */
055    public static final String MSG_WS_FOLLOWED = "ws.followed";
056
057    /**
058     * A key is pointing to the warning message text in "messages.properties"
059     * file.
060     */
061    public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
062
063    /**
064     * A key is pointing to the warning message text in "messages.properties"
065     * file.
066     */
067    public static final String MSG_WS_PRECEDED = "ws.preceded";
068
069    /**
070     * A key is pointing to the warning message text in "messages.properties"
071     * file.
072     */
073    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
074
075    /**
076     * Tokens that this check handles.
077     */
078    private final BitSet acceptableTokens;
079
080    /**
081     * Initializes acceptableTokens and message keys.
082     */
083    public ParenPadCheck() {
084        super(MSG_WS_FOLLOWED, MSG_WS_NOT_FOLLOWED, MSG_WS_PRECEDED, MSG_WS_NOT_PRECEDED);
085        acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
086    }
087
088    @Override
089    public int[] getDefaultTokens() {
090        return makeAcceptableTokens();
091    }
092
093    @Override
094    public int[] getAcceptableTokens() {
095        return makeAcceptableTokens();
096    }
097
098    @Override
099    public int[] getRequiredTokens() {
100        return CommonUtil.EMPTY_INT_ARRAY;
101    }
102
103    @Override
104    public void visitToken(DetailAST ast) {
105        switch (ast.getType()) {
106            case TokenTypes.METHOD_CALL -> {
107                processLeft(ast);
108                processRight(ast.findFirstToken(TokenTypes.RPAREN));
109            }
110
111            case TokenTypes.DOT, TokenTypes.EXPR, TokenTypes.QUESTION -> processExpression(ast);
112
113            case TokenTypes.LITERAL_FOR -> visitLiteralFor(ast);
114
115            case TokenTypes.ANNOTATION,
116                 TokenTypes.ENUM_CONSTANT_DEF,
117                 TokenTypes.LITERAL_NEW,
118                 TokenTypes.LITERAL_SYNCHRONIZED,
119                 TokenTypes.LAMBDA -> visitTokenWithOptionalParentheses(ast);
120
121            case TokenTypes.RESOURCE_SPECIFICATION -> visitResourceSpecification(ast);
122
123            default -> {
124                processLeft(ast.findFirstToken(TokenTypes.LPAREN));
125                processRight(ast.findFirstToken(TokenTypes.RPAREN));
126            }
127        }
128    }
129
130    /**
131     * Checks parens in token which may not contain parens, e.g.
132     * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
133     * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
134     * {@link TokenTypes#LAMBDA}.
135     *
136     * @param ast the token to check.
137     */
138    private void visitTokenWithOptionalParentheses(DetailAST ast) {
139        final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
140        if (parenAst != null) {
141            processLeft(parenAst);
142            processRight(ast.findFirstToken(TokenTypes.RPAREN));
143        }
144    }
145
146    /**
147     * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
148     *
149     * @param ast the token to check.
150     */
151    private void visitResourceSpecification(DetailAST ast) {
152        processLeft(ast.findFirstToken(TokenTypes.LPAREN));
153        final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
154        if (!hasPrecedingSemiColon(rparen)) {
155            processRight(rparen);
156        }
157    }
158
159    /**
160     * Checks that a token is preceded by a semicolon.
161     *
162     * @param ast the token to check
163     * @return whether a token is preceded by a semicolon
164     */
165    private static boolean hasPrecedingSemiColon(DetailAST ast) {
166        return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
167    }
168
169    /**
170     * Checks parens in {@link TokenTypes#LITERAL_FOR}.
171     *
172     * @param ast the token to check.
173     */
174    private void visitLiteralFor(DetailAST ast) {
175        final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
176        if (!isPrecedingEmptyForInit(lparen)) {
177            processLeft(lparen);
178        }
179        final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
180        if (!isFollowsEmptyForIterator(rparen)) {
181            processRight(rparen);
182        }
183    }
184
185    /**
186     * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
187     * and {@link TokenTypes#METHOD_CALL}.
188     *
189     * @param ast the token to check.
190     */
191    private void processExpression(DetailAST ast) {
192        DetailAST currentNode = ast.getFirstChild();
193        while (currentNode != null) {
194            if (currentNode.getType() == TokenTypes.LPAREN) {
195                processLeft(currentNode);
196            }
197            else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) {
198                processRight(currentNode);
199            }
200            else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) {
201                // Traverse all subtree tokens which will never be configured
202                // to be launched in visitToken()
203                currentNode = currentNode.getFirstChild();
204                continue;
205            }
206
207            // Go up after processing the last child
208            while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) {
209                currentNode = currentNode.getParent();
210            }
211            currentNode = currentNode.getNextSibling();
212        }
213    }
214
215    /**
216     * Checks whether AcceptableTokens contains the given ast.
217     *
218     * @param ast the token to check.
219     * @return true if the ast is in AcceptableTokens.
220     */
221    private boolean isAcceptableToken(DetailAST ast) {
222        return acceptableTokens.get(ast.getType());
223    }
224
225    /**
226     * Returns array of acceptable tokens.
227     *
228     * @return acceptableTokens.
229     */
230    private static int[] makeAcceptableTokens() {
231        return new int[] {TokenTypes.ANNOTATION,
232            TokenTypes.ANNOTATION_FIELD_DEF,
233            TokenTypes.CTOR_CALL,
234            TokenTypes.CTOR_DEF,
235            TokenTypes.DOT,
236            TokenTypes.ENUM_CONSTANT_DEF,
237            TokenTypes.EXPR,
238            TokenTypes.LITERAL_CATCH,
239            TokenTypes.LITERAL_DO,
240            TokenTypes.LITERAL_FOR,
241            TokenTypes.LITERAL_IF,
242            TokenTypes.LITERAL_NEW,
243            TokenTypes.LITERAL_SWITCH,
244            TokenTypes.LITERAL_SYNCHRONIZED,
245            TokenTypes.LITERAL_WHILE,
246            TokenTypes.METHOD_CALL,
247            TokenTypes.METHOD_DEF,
248            TokenTypes.QUESTION,
249            TokenTypes.RESOURCE_SPECIFICATION,
250            TokenTypes.SUPER_CTOR_CALL,
251            TokenTypes.LAMBDA,
252            TokenTypes.RECORD_DEF,
253            TokenTypes.RECORD_PATTERN_DEF,
254        };
255    }
256
257    /**
258     * Checks whether {@link TokenTypes#RPAREN} is a closing paren
259     * of a {@link TokenTypes#TYPECAST}.
260     *
261     * @param ast of a {@code TokenTypes#RPAREN} to check.
262     * @return true if ast is a closing paren of a {@code TokenTypes#TYPECAST}.
263     */
264    private static boolean isInTypecast(DetailAST ast) {
265        boolean result = false;
266        if (ast.getParent().getType() == TokenTypes.TYPECAST) {
267            final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
268            if (TokenUtil.areOnSameLine(firstRparen, ast)
269                    && firstRparen.getColumnNo() == ast.getColumnNo()) {
270                result = true;
271            }
272        }
273        return result;
274    }
275
276    /**
277     * Checks that a token follows an empty for iterator.
278     *
279     * @param ast the token to check
280     * @return whether a token follows an empty for iterator
281     */
282    private static boolean isFollowsEmptyForIterator(DetailAST ast) {
283        boolean result = false;
284        final DetailAST parent = ast.getParent();
285        // Only traditional for statements are examined, not for-each statements
286        if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
287            final DetailAST forIterator =
288                parent.findFirstToken(TokenTypes.FOR_ITERATOR);
289            result = !forIterator.hasChildren();
290        }
291        return result;
292    }
293
294    /**
295     * Checks that a token precedes an empty for initializer.
296     *
297     * @param ast the token to check
298     * @return whether a token precedes an empty for initializer
299     */
300    private static boolean isPrecedingEmptyForInit(DetailAST ast) {
301        boolean result = false;
302        final DetailAST parent = ast.getParent();
303        // Only traditional for statements are examined, not for-each statements
304        if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
305            final DetailAST forIterator =
306                    parent.findFirstToken(TokenTypes.FOR_INIT);
307            result = !forIterator.hasChildren();
308        }
309        return result;
310    }
311
312}