001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 java.util.Locale;
023
024import javax.annotation.Nullable;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
032
033/**
034 * <div>
035 * Checks for the placement of left curly braces (<code>'{'</code>) for code blocks.
036 * </div>
037 *
038 * <ul>
039 * <li>
040 * Property {@code ignoreEnums} - Allow to ignore enums when left curly brace policy is EOL.
041 * Type is {@code boolean}.
042 * Default value is {@code true}.
043 * </li>
044 * <li>
045 * Property {@code option} - Specify the policy on placement of a left curly brace
046 * (<code>'{'</code>).
047 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption}.
048 * Default value is {@code eol}.
049 * </li>
050 * <li>
051 * Property {@code tokens} - tokens to check
052 * Type is {@code java.lang.String[]}.
053 * Validation type is {@code tokenSet}.
054 * Default value is:
055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
056 * ANNOTATION_DEF</a>,
057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
058 * CLASS_DEF</a>,
059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
060 * CTOR_DEF</a>,
061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
062 * ENUM_CONSTANT_DEF</a>,
063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
064 * ENUM_DEF</a>,
065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
066 * INTERFACE_DEF</a>,
067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
068 * LAMBDA</a>,
069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE">
070 * LITERAL_CASE</a>,
071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
072 * LITERAL_CATCH</a>,
073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DEFAULT">
074 * LITERAL_DEFAULT</a>,
075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
076 * LITERAL_DO</a>,
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
078 * LITERAL_ELSE</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
080 * LITERAL_FINALLY</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
082 * LITERAL_FOR</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
084 * LITERAL_IF</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
086 * LITERAL_SWITCH</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
088 * LITERAL_SYNCHRONIZED</a>,
089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
090 * LITERAL_TRY</a>,
091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
092 * LITERAL_WHILE</a>,
093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
094 * METHOD_DEF</a>,
095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#OBJBLOCK">
096 * OBJBLOCK</a>,
097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
098 * STATIC_INIT</a>,
099 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
100 * RECORD_DEF</a>,
101 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
102 * COMPACT_CTOR_DEF</a>.
103 * </li>
104 * </ul>
105 *
106 * <p>
107 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
108 * </p>
109 *
110 * <p>
111 * Violation Message Keys:
112 * </p>
113 * <ul>
114 * <li>
115 * {@code line.break.after}
116 * </li>
117 * <li>
118 * {@code line.new}
119 * </li>
120 * <li>
121 * {@code line.previous}
122 * </li>
123 * </ul>
124 *
125 * @since 3.0
126 */
127@StatelessCheck
128public class LeftCurlyCheck
129    extends AbstractCheck {
130
131    /**
132     * A key is pointing to the warning message text in "messages.properties"
133     * file.
134     */
135    public static final String MSG_KEY_LINE_NEW = "line.new";
136
137    /**
138     * A key is pointing to the warning message text in "messages.properties"
139     * file.
140     */
141    public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
142
143    /**
144     * A key is pointing to the warning message text in "messages.properties"
145     * file.
146     */
147    public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
148
149    /** Open curly brace literal. */
150    private static final String OPEN_CURLY_BRACE = "{";
151
152    /** Allow to ignore enums when left curly brace policy is EOL. */
153    private boolean ignoreEnums = true;
154
155    /**
156     * Specify the policy on placement of a left curly brace (<code>'{'</code>).
157     */
158    private LeftCurlyOption option = LeftCurlyOption.EOL;
159
160    /**
161     * Setter to specify the policy on placement of a left curly brace (<code>'{'</code>).
162     *
163     * @param optionStr string to decode option from
164     * @throws IllegalArgumentException if unable to decode
165     * @since 3.0
166     */
167    public void setOption(String optionStr) {
168        option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
169    }
170
171    /**
172     * Setter to allow to ignore enums when left curly brace policy is EOL.
173     *
174     * @param ignoreEnums check's option for ignoring enums.
175     * @since 6.9
176     */
177    public void setIgnoreEnums(boolean ignoreEnums) {
178        this.ignoreEnums = ignoreEnums;
179    }
180
181    @Override
182    public int[] getDefaultTokens() {
183        return getAcceptableTokens();
184    }
185
186    @Override
187    public int[] getAcceptableTokens() {
188        return new int[] {
189            TokenTypes.ANNOTATION_DEF,
190            TokenTypes.CLASS_DEF,
191            TokenTypes.CTOR_DEF,
192            TokenTypes.ENUM_CONSTANT_DEF,
193            TokenTypes.ENUM_DEF,
194            TokenTypes.INTERFACE_DEF,
195            TokenTypes.LAMBDA,
196            TokenTypes.LITERAL_CASE,
197            TokenTypes.LITERAL_CATCH,
198            TokenTypes.LITERAL_DEFAULT,
199            TokenTypes.LITERAL_DO,
200            TokenTypes.LITERAL_ELSE,
201            TokenTypes.LITERAL_FINALLY,
202            TokenTypes.LITERAL_FOR,
203            TokenTypes.LITERAL_IF,
204            TokenTypes.LITERAL_SWITCH,
205            TokenTypes.LITERAL_SYNCHRONIZED,
206            TokenTypes.LITERAL_TRY,
207            TokenTypes.LITERAL_WHILE,
208            TokenTypes.METHOD_DEF,
209            TokenTypes.OBJBLOCK,
210            TokenTypes.STATIC_INIT,
211            TokenTypes.RECORD_DEF,
212            TokenTypes.COMPACT_CTOR_DEF,
213        };
214    }
215
216    @Override
217    public int[] getRequiredTokens() {
218        return CommonUtil.EMPTY_INT_ARRAY;
219    }
220
221    /**
222     * Visits token.
223     *
224     * @param ast the token to process
225     * @noinspection SwitchStatementWithTooManyBranches
226     * @noinspectionreason SwitchStatementWithTooManyBranches - we cannot reduce
227     *      the number of branches in this switch statement, since many tokens
228     *      require specific methods to find the first left curly
229     */
230    @Override
231    public void visitToken(DetailAST ast) {
232        final DetailAST startToken;
233        final DetailAST brace;
234
235        switch (ast.getType()) {
236            case TokenTypes.CTOR_DEF:
237            case TokenTypes.METHOD_DEF:
238            case TokenTypes.COMPACT_CTOR_DEF:
239                startToken = skipModifierAnnotations(ast);
240                brace = ast.findFirstToken(TokenTypes.SLIST);
241                break;
242            case TokenTypes.INTERFACE_DEF:
243            case TokenTypes.CLASS_DEF:
244            case TokenTypes.ANNOTATION_DEF:
245            case TokenTypes.ENUM_DEF:
246            case TokenTypes.ENUM_CONSTANT_DEF:
247            case TokenTypes.RECORD_DEF:
248                startToken = skipModifierAnnotations(ast);
249                brace = ast.findFirstToken(TokenTypes.OBJBLOCK);
250                break;
251            case TokenTypes.LITERAL_WHILE:
252            case TokenTypes.LITERAL_CATCH:
253            case TokenTypes.LITERAL_SYNCHRONIZED:
254            case TokenTypes.LITERAL_FOR:
255            case TokenTypes.LITERAL_TRY:
256            case TokenTypes.LITERAL_FINALLY:
257            case TokenTypes.LITERAL_DO:
258            case TokenTypes.LITERAL_IF:
259            case TokenTypes.STATIC_INIT:
260            case TokenTypes.LAMBDA:
261                startToken = ast;
262                brace = ast.findFirstToken(TokenTypes.SLIST);
263                break;
264            case TokenTypes.LITERAL_ELSE:
265                startToken = ast;
266                brace = getBraceAsFirstChild(ast);
267                break;
268            case TokenTypes.LITERAL_CASE:
269            case TokenTypes.LITERAL_DEFAULT:
270                startToken = ast;
271                brace = getBraceFromSwitchMember(ast);
272                break;
273            default:
274                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
275                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
276                // It has been done to improve coverage to 100%. I couldn't replace it with
277                // if-else-if block because code was ugly and didn't pass pmd check.
278
279                startToken = ast;
280                brace = ast.findFirstToken(TokenTypes.LCURLY);
281                break;
282        }
283
284        if (brace != null) {
285            verifyBrace(brace, startToken);
286        }
287    }
288
289    /**
290     * Gets the brace of a switch statement/ expression member.
291     *
292     * @param ast {@code DetailAST}.
293     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
294     *     {@code null} otherwise.
295     */
296    @Nullable
297    private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
298        final DetailAST brace;
299        final DetailAST parent = ast.getParent();
300        if (parent.getType() == TokenTypes.SWITCH_RULE) {
301            brace = parent.findFirstToken(TokenTypes.SLIST);
302        }
303        else {
304            brace = getBraceAsFirstChild(ast.getNextSibling());
305        }
306        return brace;
307    }
308
309    /**
310     * Gets a SLIST if it is the first child of the AST.
311     *
312     * @param ast {@code DetailAST}.
313     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
314     *     {@code null} otherwise.
315     */
316    @Nullable
317    private static DetailAST getBraceAsFirstChild(DetailAST ast) {
318        DetailAST brace = null;
319        if (ast != null) {
320            final DetailAST candidate = ast.getFirstChild();
321            if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
322                brace = candidate;
323            }
324        }
325        return brace;
326    }
327
328    /**
329     * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation.
330     *
331     * @param ast {@code DetailAST}.
332     * @return {@code DetailAST}.
333     */
334    private static DetailAST skipModifierAnnotations(DetailAST ast) {
335        DetailAST resultNode = ast;
336        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
337
338        if (modifiers != null) {
339            final DetailAST lastAnnotation = findLastAnnotation(modifiers);
340
341            if (lastAnnotation != null) {
342                if (lastAnnotation.getNextSibling() == null) {
343                    resultNode = modifiers.getNextSibling();
344                }
345                else {
346                    resultNode = lastAnnotation.getNextSibling();
347                }
348            }
349        }
350        return resultNode;
351    }
352
353    /**
354     * Find the last token of type {@code TokenTypes.ANNOTATION}
355     * under the given set of modifiers.
356     *
357     * @param modifiers {@code DetailAST}.
358     * @return {@code DetailAST} or null if there are no annotations.
359     */
360    private static DetailAST findLastAnnotation(DetailAST modifiers) {
361        DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
362        while (annotation != null && annotation.getNextSibling() != null
363               && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
364            annotation = annotation.getNextSibling();
365        }
366        return annotation;
367    }
368
369    /**
370     * Verifies that a specified left curly brace is placed correctly
371     * according to policy.
372     *
373     * @param brace token for left curly brace
374     * @param startToken token for start of expression
375     */
376    private void verifyBrace(final DetailAST brace,
377                             final DetailAST startToken) {
378        final String braceLine = getLine(brace.getLineNo() - 1);
379
380        // Check for being told to ignore, or have '{}' which is a special case
381        if (braceLine.length() <= brace.getColumnNo() + 1
382                || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
383            if (option == LeftCurlyOption.NL) {
384                if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
385                    log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
386                }
387            }
388            else if (option == LeftCurlyOption.EOL) {
389                validateEol(brace, braceLine);
390            }
391            else if (!TokenUtil.areOnSameLine(startToken, brace)) {
392                validateNewLinePosition(brace, startToken, braceLine);
393            }
394        }
395    }
396
397    /**
398     * Validate EOL case.
399     *
400     * @param brace brace AST
401     * @param braceLine line content
402     */
403    private void validateEol(DetailAST brace, String braceLine) {
404        if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
405            log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
406        }
407        if (!hasLineBreakAfter(brace)) {
408            log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
409        }
410    }
411
412    /**
413     * Validate token on new Line position.
414     *
415     * @param brace brace AST
416     * @param startToken start Token
417     * @param braceLine content of line with Brace
418     */
419    private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
420        // not on the same line
421        if (startToken.getLineNo() + 1 == brace.getLineNo()) {
422            if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
423                log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
424            }
425            else {
426                log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
427            }
428        }
429        else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
430            log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
431        }
432    }
433
434    /**
435     * Checks if left curly has line break after.
436     *
437     * @param leftCurly
438     *        Left curly token.
439     * @return
440     *        True, left curly has line break after.
441     */
442    private boolean hasLineBreakAfter(DetailAST leftCurly) {
443        DetailAST nextToken = null;
444        if (leftCurly.getType() == TokenTypes.SLIST) {
445            nextToken = leftCurly.getFirstChild();
446        }
447        else {
448            if (!ignoreEnums
449                    && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
450                nextToken = leftCurly.getNextSibling();
451            }
452        }
453        return nextToken == null
454                || nextToken.getType() == TokenTypes.RCURLY
455                || !TokenUtil.areOnSameLine(leftCurly, nextToken);
456    }
457
458}