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.blocks;
021
022import javax.annotation.Nullable;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.NullUtil;
029import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
030
031/**
032 * <div>
033 * Checks the placement of right curly braces (<code>'}'</code>) for code blocks, following
034 * the <a href="https://google.github.io/styleguide/javaguide.html#s4.1-braces">
035 *     Google Java Style Guide </a>.
036 * <p>
037 * For nonempty blocks the right curly brace must begin its own line,
038 * unless it is followed by {@code else}, {@code catch}, {@code finally}, or a comma,
039 * in which case no line break follows it.
040 * </p>
041 * <p>
042 * For empty blocks, either {@code K&R} style or the concise {@code {}} form is
043 * allowed, except within a multi-block statement ({@code if/else}, {@code try/catch/finally}).
044 * </p>
045 * </div>
046 *
047 * @since 14.2.0
048 */
049@StatelessCheck
050public class GoogleRightCurlyCheck extends AbstractCheck {
051
052    /**
053     * A key is pointing to the warning message text in "messages.properties" file.
054     */
055    public static final String MSG_KEY_LINE_ALONE = "line.alone";
056
057    /**
058     * A key is pointing to the warning message text in "messages.properties" file.
059     */
060    public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
061
062    /**
063     * A key is pointing to the warning message text in "messages.properties" file.
064     */
065    public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
066
067    /**
068     * A key is pointing to the warning message text in "messages.properties" file.
069     */
070    public static final String MSG_KEY_CONCISE_BLOCK = "empty.block.concise";
071
072    /**
073     * A key is pointing to the warning message text in "messages.properties" file.
074     */
075    public static final String MSG_KEY_LINE_SAME = "line.same";
076
077    /**
078     * Creates a new {@code GoogleRightCurlyCheck} instance.
079     */
080    public GoogleRightCurlyCheck() {
081        // no code by default
082    }
083
084    @Override
085    public int[] getDefaultTokens() {
086        return getRequiredTokens();
087    }
088
089    @Override
090    public int[] getAcceptableTokens() {
091        return new int[] {
092            TokenTypes.LITERAL_IF,
093            TokenTypes.LITERAL_ELSE,
094            TokenTypes.LITERAL_TRY,
095            TokenTypes.LITERAL_CATCH,
096            TokenTypes.LITERAL_FINALLY,
097            TokenTypes.LITERAL_DO,
098            TokenTypes.CLASS_DEF,
099            TokenTypes.INTERFACE_DEF,
100            TokenTypes.RECORD_DEF,
101            TokenTypes.ANNOTATION_DEF,
102            TokenTypes.ENUM_DEF,
103            TokenTypes.METHOD_DEF,
104            TokenTypes.CTOR_DEF,
105            TokenTypes.COMPACT_CTOR_DEF,
106            TokenTypes.LITERAL_FOR,
107            TokenTypes.LITERAL_WHILE,
108            TokenTypes.LITERAL_SWITCH,
109            TokenTypes.LITERAL_CASE,
110            TokenTypes.LITERAL_DEFAULT,
111            TokenTypes.STATIC_INIT,
112            TokenTypes.INSTANCE_INIT,
113            TokenTypes.LITERAL_SYNCHRONIZED,
114        };
115    }
116
117    @Override
118    public int[] getRequiredTokens() {
119        return getAcceptableTokens();
120    }
121
122    @Override
123    public boolean isCommentNodesRequired() {
124        return true;
125    }
126
127    @Override
128    public void visitToken(DetailAST ast) {
129        DetailAST rightCurly = null;
130        switch (ast.getType()) {
131            case TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF,
132                 TokenTypes.ANNOTATION_DEF, TokenTypes.RECORD_DEF, TokenTypes.ENUM_DEF -> {
133                final DetailAST child =
134                        NullUtil.notNull(ast.findFirstToken(TokenTypes.OBJBLOCK));
135                rightCurly = NullUtil.notNull(child.findFirstToken(TokenTypes.RCURLY));
136            }
137
138            case TokenTypes.LITERAL_SWITCH -> rightCurly = ast.getLastChild();
139
140            case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> handleCaseAndDefault(ast);
141
142            default -> {
143                final DetailAST child = ast.findFirstToken(TokenTypes.SLIST);
144                if (child != null) {
145                    rightCurly = child.getLastChild();
146                }
147            }
148        }
149        if (rightCurly != null) {
150            checkRightBrace(ast, rightCurly);
151        }
152    }
153
154    /**
155     * Checks the right curly brace placement for {@code case} and
156     * {@code default} blocks, covering both old style {@code case X:}
157     * and new style {@code case X ->} switch syntax.
158     *
159     * <p>For old-style syntax, a case label may be followed by multiple
160     * {@code {}} blocks in sequence, and each such block's right curly
161     * brace is checked. For new-style syntax, the block following the
162     * arrow (e.g. {@code case X -> { ... }}) is checked,
163     * expression with no block (e.g. {@code case X -> expr;}) is skipped.
164     *
165     * @param ast the {@code case} or {@code default} token
166     */
167    private void handleCaseAndDefault(DetailAST ast) {
168        DetailAST startToken = ast;
169        if (ast.getParent().getType() == TokenTypes.CASE_GROUP) {
170            final DetailAST nextSibling = startToken.getNextSibling();
171            if (nextSibling != null) {
172                startToken = nextSibling.findFirstToken(TokenTypes.SLIST);
173            }
174        }
175        for (DetailAST current = startToken; current != null;
176             current = current.getNextSibling()) {
177            if (current.getType() == TokenTypes.SLIST) {
178                final DetailAST rightBrace = NullUtil.notNull(current.getLastChild());
179                checkRightBrace(ast, rightBrace);
180            }
181        }
182    }
183
184    /**
185     * Logs violation message for given brace token.
186     *
187     * @param message the violation message key.
188     * @param brace the right curly brace.
189     */
190    private void logViolations(String message, DetailAST brace) {
191        log(brace, message, brace.getText(), brace.getColumnNo() + 1);
192    }
193
194    /**
195     * Checks that a right curly brace is placed correctly.
196     *
197     * <p>If the block is part of a multi-block statement (e.g. {@code if/else},
198     * {@code try/catch/finally}, or {@code do/while}), the closing brace must be
199     * on the same line as the next block's starting keyword. Otherwise, the
200     * brace must be alone on its own line, unless the block is empty, in which
201     * case the concise {@code {}} form is allowed.
202     *
203     * @param currentBlock the block whose right curly brace is being checked
204     * @param brace the right curly brace token
205     */
206    private void checkRightBrace(DetailAST currentBlock, DetailAST brace) {
207        final DetailAST nextToken = getNextToken(brace);
208        final boolean hasContentAround = contentAround(brace, nextToken);
209        if (nextToken != null && isPartOfMultiBlock(currentBlock, nextToken)) {
210            checkMultiBlockStatement(currentBlock, brace, nextToken);
211        }
212        else if (currentBlock.getParent().getType() == TokenTypes.LITERAL_ELSE
213                || TokenUtil.isOfType(currentBlock, TokenTypes.LITERAL_ELSE,
214                TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_FINALLY)) {
215            if (hasContentAround) {
216                logViolations(MSG_KEY_LINE_ALONE, brace);
217            }
218        }
219        else if (isEmpty(brace)) {
220            verifyEmptyBlock(brace, nextToken);
221        }
222        else if (hasContentAround) {
223            logViolations(MSG_KEY_LINE_ALONE, brace);
224        }
225    }
226
227    /**
228     * Checks that the right curly brace of a multi-block statement (e.g. {@code if/else},
229     * {@code try/catch/finally}, {@code do/while}) is placed correctly relative to the next block.
230     *
231     * @param currentBlock the current block
232     * @param brace the right curly brace
233     * @param nextBlock the next block in multi-block statement
234     */
235    private void checkMultiBlockStatement(DetailAST currentBlock, DetailAST brace,
236        DetailAST nextBlock) {
237        if (TokenUtil.areOnSameLine(brace, nextBlock)) {
238            if (hasContentOnLeftSide(brace) && !(currentBlock.getType() == TokenTypes.LITERAL_DO
239                    && isEmpty(brace))) {
240                logViolations(MSG_KEY_LINE_BREAK_BEFORE, brace);
241            }
242        }
243        else {
244            logViolations(MSG_KEY_LINE_SAME, brace);
245        }
246    }
247
248    /**
249     * Checks empty block which should be concise and alone.
250     *
251     * @param brace the right curly token.
252     * @param nextToken the token after right curly brace.
253     */
254    private void verifyEmptyBlock(DetailAST brace, @Nullable DetailAST nextToken) {
255        if (isNotConcise(brace)) {
256            logViolations(MSG_KEY_CONCISE_BLOCK, brace);
257        }
258        else if (nextToken != null
259                && hasContentOnRightSide(brace, nextToken)) {
260            logViolations(MSG_KEY_LINE_BREAK_AFTER, brace);
261        }
262    }
263
264    /**
265     * Checks if the right curly has content around.
266     *
267     * @param brace the right curly brace
268     * @param nextToken the next token after right curly
269     * @return {@code true} if right curly has content on its left or right.
270     */
271    private static boolean contentAround(DetailAST brace, @Nullable DetailAST nextToken) {
272        return nextToken != null
273                && hasContentOnRightSide(brace, nextToken)
274                || hasContentOnLeftSide(brace);
275    }
276
277    /**
278     * Checks whether the current block is part of a multi-block statement ({@code if/else},
279     * {@code try/catch/finally}, or {@code do/while}).
280     *
281     * @param currentBlock the current block
282     * @param nextBlock the block following {@code ast}
283     * @return {@code true} if {@code ast} and {@code nextBlock} belong to
284     *         the same multi-block statement
285     */
286    private static boolean isPartOfMultiBlock(DetailAST currentBlock, DetailAST nextBlock) {
287        final int nextBlockType = nextBlock.getType();
288        return switch (currentBlock.getType()) {
289            case TokenTypes.LITERAL_IF ->
290                nextBlockType == TokenTypes.LITERAL_ELSE;
291            case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH ->
292                nextBlockType == TokenTypes.LITERAL_CATCH
293                    || nextBlockType == TokenTypes.LITERAL_FINALLY;
294            case TokenTypes.LITERAL_DO -> true;
295            default -> false;
296        };
297    }
298
299    /**
300     * Checks if the block is not concise and has content on left side of right brace.
301     *
302     * @param brace the right curly brace token
303     * @return {@code true} if the brace has content on left.
304     */
305    private static boolean hasContentOnLeftSide(DetailAST brace) {
306        DetailAST previousToken = brace.getPreviousSibling();
307        if (previousToken == null) {
308            previousToken = brace.getParent();
309        }
310        if (previousToken.getType() != TokenTypes.SLIST) {
311            while (previousToken.hasChildren()) {
312                previousToken = previousToken.getLastChild();
313            }
314        }
315
316        return TokenUtil.areOnSameLine(brace, previousToken)
317                && !TokenUtil.isOfType(brace.getPreviousSibling(),
318                TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.COMMA);
319    }
320
321    /**
322     * Checks if the right curly brace is part multi-block statement or no
323     * content on right side of right brace.
324     *
325     * @param brace the right curly brace token
326     * @param nextToken the next token of right curly.
327     * @return {@code true} if the brace is on the same line as the previous sibling
328     *     or parent if no sibling exists
329     */
330    private static boolean hasContentOnRightSide(DetailAST brace, DetailAST nextToken) {
331        final boolean nextIsValid = TokenUtil.isOfType(nextToken.getPreviousSibling(),
332                TokenTypes.EXPR, TokenTypes.VARIABLE_DEF, TokenTypes.ELIST, TokenTypes.LAMBDA)
333                || TokenUtil.isCommentType(nextToken.getType());
334        return !nextIsValid && TokenUtil.areOnSameLine(brace, nextToken);
335    }
336
337    /**
338     * Checks if block is empty.
339     *
340     * @param brace the right curly brace.
341     * @return {@code true} if the block is empty.
342     */
343    private static boolean isEmpty(DetailAST brace) {
344        final DetailAST previousSibling = brace.getPreviousSibling();
345        return previousSibling == null || previousSibling.getType() == TokenTypes.LCURLY;
346    }
347
348    /**
349     * Checks if the block is not {@code K&R} style or concise {@code {}}.
350     *
351     * @param brace right curly token
352     * @return {@code true} if block is not concise.
353     */
354    private static boolean isNotConcise(DetailAST brace) {
355        final DetailAST lcurly = brace.getParent();
356        return lcurly.getLineNo() == brace.getLineNo()
357                && lcurly.getColumnNo() + 1 != brace.getColumnNo();
358    }
359
360    /**
361     * Traverses up the AST to find the next sibling token after the right curly brace.
362     *
363     * @param node ast token
364     * @return the next sibling token, or {@code null} if none exists
365     */
366    @Nullable
367    private static DetailAST getNextToken(DetailAST node) {
368        DetailAST current = node;
369        DetailAST nextToken = null;
370        while (current != null && nextToken == null) {
371            nextToken = current.getNextSibling();
372            current = current.getParent();
373        }
374        return nextToken;
375    }
376
377}