001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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        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                final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
319                brace = objBlock;
320
321                if (objBlock != null) {
322                    brace = objBlock.getFirstChild();
323                }
324                break;
325            case TokenTypes.LITERAL_WHILE:
326            case TokenTypes.LITERAL_CATCH:
327            case TokenTypes.LITERAL_SYNCHRONIZED:
328            case TokenTypes.LITERAL_FOR:
329            case TokenTypes.LITERAL_TRY:
330            case TokenTypes.LITERAL_FINALLY:
331            case TokenTypes.LITERAL_DO:
332            case TokenTypes.LITERAL_IF:
333            case TokenTypes.STATIC_INIT:
334            case TokenTypes.LAMBDA:
335                startToken = ast;
336                brace = ast.findFirstToken(TokenTypes.SLIST);
337                break;
338            case TokenTypes.LITERAL_ELSE:
339                startToken = ast;
340                brace = getBraceAsFirstChild(ast);
341                break;
342            case TokenTypes.LITERAL_CASE:
343            case TokenTypes.LITERAL_DEFAULT:
344                startToken = ast;
345                brace = getBraceFromSwitchMember(ast);
346                break;
347            default:
348                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
349                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
350                // It has been done to improve coverage to 100%. I couldn't replace it with
351                // if-else-if block because code was ugly and didn't pass pmd check.
352
353                startToken = ast;
354                brace = ast.findFirstToken(TokenTypes.LCURLY);
355                break;
356        }
357
358        if (brace != null) {
359            verifyBrace(brace, startToken);
360        }
361    }
362
363    /**
364     * Gets the brace of a switch statement/ expression member.
365     *
366     * @param ast {@code DetailAST}.
367     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
368     * {@code null} otherwise.
369     */
370    private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
371        final DetailAST brace;
372        final DetailAST parent = ast.getParent();
373        if (parent.getType() == TokenTypes.SWITCH_RULE) {
374            brace = parent.findFirstToken(TokenTypes.SLIST);
375        }
376        else {
377            brace = getBraceAsFirstChild(ast.getNextSibling());
378        }
379        return brace;
380    }
381
382    /**
383     * Gets a SLIST if it is the first child of the AST.
384     *
385     * @param ast {@code DetailAST}.
386     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
387     *     {@code null} otherwise.
388     */
389    private static DetailAST getBraceAsFirstChild(DetailAST ast) {
390        DetailAST brace = null;
391        if (ast != null) {
392            final DetailAST candidate = ast.getFirstChild();
393            if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
394                brace = candidate;
395            }
396        }
397        return brace;
398    }
399
400    /**
401     * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation.
402     *
403     * @param ast {@code DetailAST}.
404     * @return {@code DetailAST}.
405     */
406    private static DetailAST skipModifierAnnotations(DetailAST ast) {
407        DetailAST resultNode = ast;
408        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
409
410        if (modifiers != null) {
411            final DetailAST lastAnnotation = findLastAnnotation(modifiers);
412
413            if (lastAnnotation != null) {
414                if (lastAnnotation.getNextSibling() == null) {
415                    resultNode = modifiers.getNextSibling();
416                }
417                else {
418                    resultNode = lastAnnotation.getNextSibling();
419                }
420            }
421        }
422        return resultNode;
423    }
424
425    /**
426     * Find the last token of type {@code TokenTypes.ANNOTATION}
427     * under the given set of modifiers.
428     *
429     * @param modifiers {@code DetailAST}.
430     * @return {@code DetailAST} or null if there are no annotations.
431     */
432    private static DetailAST findLastAnnotation(DetailAST modifiers) {
433        DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
434        while (annotation != null && annotation.getNextSibling() != null
435               && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
436            annotation = annotation.getNextSibling();
437        }
438        return annotation;
439    }
440
441    /**
442     * Verifies that a specified left curly brace is placed correctly
443     * according to policy.
444     *
445     * @param brace token for left curly brace
446     * @param startToken token for start of expression
447     */
448    private void verifyBrace(final DetailAST brace,
449                             final DetailAST startToken) {
450        final String braceLine = getLine(brace.getLineNo() - 1);
451
452        // Check for being told to ignore, or have '{}' which is a special case
453        if (braceLine.length() <= brace.getColumnNo() + 1
454                || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
455            if (option == LeftCurlyOption.NL) {
456                if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
457                    log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
458                }
459            }
460            else if (option == LeftCurlyOption.EOL) {
461                validateEol(brace, braceLine);
462            }
463            else if (!TokenUtil.areOnSameLine(startToken, brace)) {
464                validateNewLinePosition(brace, startToken, braceLine);
465            }
466        }
467    }
468
469    /**
470     * Validate EOL case.
471     *
472     * @param brace brace AST
473     * @param braceLine line content
474     */
475    private void validateEol(DetailAST brace, String braceLine) {
476        if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
477            log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
478        }
479        if (!hasLineBreakAfter(brace)) {
480            log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
481        }
482    }
483
484    /**
485     * Validate token on new Line position.
486     *
487     * @param brace brace AST
488     * @param startToken start Token
489     * @param braceLine content of line with Brace
490     */
491    private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
492        // not on the same line
493        if (startToken.getLineNo() + 1 == brace.getLineNo()) {
494            if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
495                log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
496            }
497            else {
498                log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
499            }
500        }
501        else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
502            log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
503        }
504    }
505
506    /**
507     * Checks if left curly has line break after.
508     *
509     * @param leftCurly
510     *        Left curly token.
511     * @return
512     *        True, left curly has line break after.
513     */
514    private boolean hasLineBreakAfter(DetailAST leftCurly) {
515        DetailAST nextToken = null;
516        if (leftCurly.getType() == TokenTypes.SLIST) {
517            nextToken = leftCurly.getFirstChild();
518        }
519        else {
520            if (!ignoreEnums
521                    && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
522                nextToken = leftCurly.getNextSibling();
523            }
524        }
525        return nextToken == null
526                || nextToken.getType() == TokenTypes.RCURLY
527                || !TokenUtil.areOnSameLine(leftCurly, nextToken);
528    }
529
530}