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