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