001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 option} - Specify the policy on placement of a left curly brace
038 * (<code>'{'</code>).
039 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption}.
040 * Default value is {@code eol}.
041 * </li>
042 * <li>
043 * Property {@code ignoreEnums} - Allow to ignore enums when left curly brace policy is EOL.
044 * Type is {@code boolean}.
045 * Default value is {@code true}.
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 * To configure the check:
104 * </p>
105 * <pre>
106 * &lt;module name="LeftCurly"/&gt;
107 * </pre>
108 * <pre>
109 * class Test
110 * { // Violation - '{' should be on the previous line
111 *   private interface TestInterface
112 *   { // Violation - '{' should be on the previous line
113 *   }
114 *
115 *   private
116 *   class
117 *   MyClass { // OK
118 *   }
119 *
120 *   enum Colors {RED, // OK
121 *     BLUE,
122 *     GREEN;
123 *   }
124 * }
125 * </pre>
126 * <p>
127 * To configure the check to apply the {@code nl} policy to type blocks:
128 * </p>
129 * <pre>
130 * &lt;module name=&quot;LeftCurly&quot;&gt;
131 *   &lt;property name=&quot;option&quot; value=&quot;nl&quot;/&gt;
132 *   &lt;property name=&quot;tokens&quot; value=&quot;CLASS_DEF,INTERFACE_DEF&quot;/&gt;
133 * &lt;/module&gt;
134 * </pre>
135 * <pre>
136 * class Test
137 * { // OK
138 *   private interface TestInterface
139 *   { // OK
140 *   }
141 *
142 *   private
143 *   class
144 *   MyClass { // Violation - '{' should be on a new line
145 *   }
146 *
147 *   enum Colors {RED, // OK
148 *     BLUE,
149 *     GREEN;
150 *   }
151 * }
152 * </pre>
153 * <p>
154 * An example of how to configure the check to validate enum definitions:
155 * </p>
156 * <pre>
157 * &lt;module name=&quot;LeftCurly&quot;&gt;
158 *   &lt;property name=&quot;ignoreEnums&quot; value=&quot;false&quot;/&gt;
159 * &lt;/module&gt;
160 * </pre>
161 * <pre>
162 * class Test
163 * { // Violation - '{' should be on the previous line
164 *   private interface TestInterface
165 *   { // Violation - '{' should be on the previous line
166 *   }
167 *
168 *   private
169 *   class
170 *   MyClass { // OK
171 *   }
172 *
173 *   enum Colors {RED, // Violation - '{' should have line break after
174 *   BLUE,
175 *   GREEN;
176 *   }
177 * }
178 * </pre>
179 * <p>
180 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
181 * </p>
182 * <p>
183 * Violation Message Keys:
184 * </p>
185 * <ul>
186 * <li>
187 * {@code line.break.after}
188 * </li>
189 * <li>
190 * {@code line.new}
191 * </li>
192 * <li>
193 * {@code line.previous}
194 * </li>
195 * </ul>
196 *
197 * @since 3.0
198 */
199@StatelessCheck
200public class LeftCurlyCheck
201    extends AbstractCheck {
202
203    /**
204     * A key is pointing to the warning message text in "messages.properties"
205     * file.
206     */
207    public static final String MSG_KEY_LINE_NEW = "line.new";
208
209    /**
210     * A key is pointing to the warning message text in "messages.properties"
211     * file.
212     */
213    public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
214
215    /**
216     * A key is pointing to the warning message text in "messages.properties"
217     * file.
218     */
219    public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
220
221    /** Open curly brace literal. */
222    private static final String OPEN_CURLY_BRACE = "{";
223
224    /** Allow to ignore enums when left curly brace policy is EOL. */
225    private boolean ignoreEnums = true;
226
227    /**
228     * Specify the policy on placement of a left curly brace (<code>'{'</code>).
229     * */
230    private LeftCurlyOption option = LeftCurlyOption.EOL;
231
232    /**
233     * Setter to specify the policy on placement of a left curly brace (<code>'{'</code>).
234     *
235     * @param optionStr string to decode option from
236     * @throws IllegalArgumentException if unable to decode
237     */
238    public void setOption(String optionStr) {
239        option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
240    }
241
242    /**
243     * Setter to allow to ignore enums when left curly brace policy is EOL.
244     *
245     * @param ignoreEnums check's option for ignoring enums.
246     */
247    public void setIgnoreEnums(boolean ignoreEnums) {
248        this.ignoreEnums = ignoreEnums;
249    }
250
251    @Override
252    public int[] getDefaultTokens() {
253        return getAcceptableTokens();
254    }
255
256    @Override
257    public int[] getAcceptableTokens() {
258        return new int[] {
259            TokenTypes.ANNOTATION_DEF,
260            TokenTypes.CLASS_DEF,
261            TokenTypes.CTOR_DEF,
262            TokenTypes.ENUM_CONSTANT_DEF,
263            TokenTypes.ENUM_DEF,
264            TokenTypes.INTERFACE_DEF,
265            TokenTypes.LAMBDA,
266            TokenTypes.LITERAL_CASE,
267            TokenTypes.LITERAL_CATCH,
268            TokenTypes.LITERAL_DEFAULT,
269            TokenTypes.LITERAL_DO,
270            TokenTypes.LITERAL_ELSE,
271            TokenTypes.LITERAL_FINALLY,
272            TokenTypes.LITERAL_FOR,
273            TokenTypes.LITERAL_IF,
274            TokenTypes.LITERAL_SWITCH,
275            TokenTypes.LITERAL_SYNCHRONIZED,
276            TokenTypes.LITERAL_TRY,
277            TokenTypes.LITERAL_WHILE,
278            TokenTypes.METHOD_DEF,
279            TokenTypes.OBJBLOCK,
280            TokenTypes.STATIC_INIT,
281            TokenTypes.RECORD_DEF,
282            TokenTypes.COMPACT_CTOR_DEF,
283        };
284    }
285
286    @Override
287    public int[] getRequiredTokens() {
288        return CommonUtil.EMPTY_INT_ARRAY;
289    }
290
291    /**
292     * We cannot reduce the number of branches in this switch statement,
293     * since many tokens require specific methods to find the first left
294     * curly.
295     *
296     * @param ast the token to process
297     * @noinspection SwitchStatementWithTooManyBranches
298     */
299    @Override
300    public void visitToken(DetailAST ast) {
301        final DetailAST startToken;
302        final DetailAST brace;
303
304        switch (ast.getType()) {
305            case TokenTypes.CTOR_DEF:
306            case TokenTypes.METHOD_DEF:
307            case TokenTypes.COMPACT_CTOR_DEF:
308                startToken = skipModifierAnnotations(ast);
309                brace = ast.findFirstToken(TokenTypes.SLIST);
310                break;
311            case TokenTypes.INTERFACE_DEF:
312            case TokenTypes.CLASS_DEF:
313            case TokenTypes.ANNOTATION_DEF:
314            case TokenTypes.ENUM_DEF:
315            case TokenTypes.ENUM_CONSTANT_DEF:
316            case TokenTypes.RECORD_DEF:
317                startToken = skipModifierAnnotations(ast);
318                brace = ast.findFirstToken(TokenTypes.OBJBLOCK);
319                break;
320            case TokenTypes.LITERAL_WHILE:
321            case TokenTypes.LITERAL_CATCH:
322            case TokenTypes.LITERAL_SYNCHRONIZED:
323            case TokenTypes.LITERAL_FOR:
324            case TokenTypes.LITERAL_TRY:
325            case TokenTypes.LITERAL_FINALLY:
326            case TokenTypes.LITERAL_DO:
327            case TokenTypes.LITERAL_IF:
328            case TokenTypes.STATIC_INIT:
329            case TokenTypes.LAMBDA:
330                startToken = ast;
331                brace = ast.findFirstToken(TokenTypes.SLIST);
332                break;
333            case TokenTypes.LITERAL_ELSE:
334                startToken = ast;
335                brace = getBraceAsFirstChild(ast);
336                break;
337            case TokenTypes.LITERAL_CASE:
338            case TokenTypes.LITERAL_DEFAULT:
339                startToken = ast;
340                brace = getBraceFromSwitchMember(ast);
341                break;
342            default:
343                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
344                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
345                // It has been done to improve coverage to 100%. I couldn't replace it with
346                // if-else-if block because code was ugly and didn't pass pmd check.
347
348                startToken = ast;
349                brace = ast.findFirstToken(TokenTypes.LCURLY);
350                break;
351        }
352
353        if (brace != null) {
354            verifyBrace(brace, startToken);
355        }
356    }
357
358    /**
359     * Gets the brace of a switch statement/ expression member.
360     *
361     * @param ast {@code DetailAST}.
362     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
363     *     {@code null} otherwise.
364     */
365    private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
366        final DetailAST brace;
367        final DetailAST parent = ast.getParent();
368        if (parent.getType() == TokenTypes.SWITCH_RULE) {
369            brace = parent.findFirstToken(TokenTypes.SLIST);
370        }
371        else {
372            brace = getBraceAsFirstChild(ast.getNextSibling());
373        }
374        return brace;
375    }
376
377    /**
378     * Gets a SLIST if it is the first child of the AST.
379     *
380     * @param ast {@code DetailAST}.
381     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
382     *     {@code null} otherwise.
383     */
384    private static DetailAST getBraceAsFirstChild(DetailAST ast) {
385        DetailAST brace = null;
386        if (ast != null) {
387            final DetailAST candidate = ast.getFirstChild();
388            if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
389                brace = candidate;
390            }
391        }
392        return brace;
393    }
394
395    /**
396     * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation.
397     *
398     * @param ast {@code DetailAST}.
399     * @return {@code DetailAST}.
400     */
401    private static DetailAST skipModifierAnnotations(DetailAST ast) {
402        DetailAST resultNode = ast;
403        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
404
405        if (modifiers != null) {
406            final DetailAST lastAnnotation = findLastAnnotation(modifiers);
407
408            if (lastAnnotation != null) {
409                if (lastAnnotation.getNextSibling() == null) {
410                    resultNode = modifiers.getNextSibling();
411                }
412                else {
413                    resultNode = lastAnnotation.getNextSibling();
414                }
415            }
416        }
417        return resultNode;
418    }
419
420    /**
421     * Find the last token of type {@code TokenTypes.ANNOTATION}
422     * under the given set of modifiers.
423     *
424     * @param modifiers {@code DetailAST}.
425     * @return {@code DetailAST} or null if there are no annotations.
426     */
427    private static DetailAST findLastAnnotation(DetailAST modifiers) {
428        DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
429        while (annotation != null && annotation.getNextSibling() != null
430               && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
431            annotation = annotation.getNextSibling();
432        }
433        return annotation;
434    }
435
436    /**
437     * Verifies that a specified left curly brace is placed correctly
438     * according to policy.
439     *
440     * @param brace token for left curly brace
441     * @param startToken token for start of expression
442     */
443    private void verifyBrace(final DetailAST brace,
444                             final DetailAST startToken) {
445        final String braceLine = getLine(brace.getLineNo() - 1);
446
447        // Check for being told to ignore, or have '{}' which is a special case
448        if (braceLine.length() <= brace.getColumnNo() + 1
449                || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
450            if (option == LeftCurlyOption.NL) {
451                if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
452                    log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
453                }
454            }
455            else if (option == LeftCurlyOption.EOL) {
456                validateEol(brace, braceLine);
457            }
458            else if (!TokenUtil.areOnSameLine(startToken, brace)) {
459                validateNewLinePosition(brace, startToken, braceLine);
460            }
461        }
462    }
463
464    /**
465     * Validate EOL case.
466     *
467     * @param brace brace AST
468     * @param braceLine line content
469     */
470    private void validateEol(DetailAST brace, String braceLine) {
471        if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
472            log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
473        }
474        if (!hasLineBreakAfter(brace)) {
475            log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
476        }
477    }
478
479    /**
480     * Validate token on new Line position.
481     *
482     * @param brace brace AST
483     * @param startToken start Token
484     * @param braceLine content of line with Brace
485     */
486    private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
487        // not on the same line
488        if (startToken.getLineNo() + 1 == brace.getLineNo()) {
489            if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
490                log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
491            }
492            else {
493                log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
494            }
495        }
496        else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
497            log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
498        }
499    }
500
501    /**
502     * Checks if left curly has line break after.
503     *
504     * @param leftCurly
505     *        Left curly token.
506     * @return
507     *        True, left curly has line break after.
508     */
509    private boolean hasLineBreakAfter(DetailAST leftCurly) {
510        DetailAST nextToken = null;
511        if (leftCurly.getType() == TokenTypes.SLIST) {
512            nextToken = leftCurly.getFirstChild();
513        }
514        else {
515            if (!ignoreEnums
516                    && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
517                nextToken = leftCurly.getNextSibling();
518            }
519        }
520        return nextToken == null
521                || nextToken.getType() == TokenTypes.RCURLY
522                || !TokenUtil.areOnSameLine(leftCurly, nextToken);
523    }
524
525}