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