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