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