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