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