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