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