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