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 org.apache.commons.beanutils.ConversionException;
025
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
030
031/**
032 * <p>
033 * Checks the placement of left curly braces on types, methods and
034 * other blocks:
035 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},  {@link
036 * TokenTypes#LITERAL_DO LITERAL_DO},  {@link TokenTypes#LITERAL_ELSE
037 * LITERAL_ELSE},  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},  {@link
038 * TokenTypes#LITERAL_FOR LITERAL_FOR},  {@link TokenTypes#LITERAL_IF
039 * LITERAL_IF},  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},  {@link
040 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},  {@link
041 * TokenTypes#LITERAL_TRY LITERAL_TRY},  {@link TokenTypes#LITERAL_WHILE
042 * LITERAL_WHILE},  {@link TokenTypes#STATIC_INIT STATIC_INIT},
043 * {@link TokenTypes#LAMBDA LAMBDA}.
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 AbstractCheck {
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            TokenTypes.LAMBDA,
171        };
172    }
173
174    @Override
175    public int[] getRequiredTokens() {
176        return CommonUtils.EMPTY_INT_ARRAY;
177    }
178
179    @Override
180    public void visitToken(DetailAST ast) {
181        final DetailAST startToken;
182        DetailAST brace;
183
184        switch (ast.getType()) {
185            case TokenTypes.CTOR_DEF:
186            case TokenTypes.METHOD_DEF:
187                startToken = skipAnnotationOnlyLines(ast);
188                brace = ast.findFirstToken(TokenTypes.SLIST);
189                break;
190            case TokenTypes.INTERFACE_DEF:
191            case TokenTypes.CLASS_DEF:
192            case TokenTypes.ANNOTATION_DEF:
193            case TokenTypes.ENUM_DEF:
194            case TokenTypes.ENUM_CONSTANT_DEF:
195                startToken = skipAnnotationOnlyLines(ast);
196                final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
197                brace = objBlock;
198
199                if (objBlock != null) {
200                    brace = objBlock.getFirstChild();
201                }
202                break;
203            case TokenTypes.LITERAL_WHILE:
204            case TokenTypes.LITERAL_CATCH:
205            case TokenTypes.LITERAL_SYNCHRONIZED:
206            case TokenTypes.LITERAL_FOR:
207            case TokenTypes.LITERAL_TRY:
208            case TokenTypes.LITERAL_FINALLY:
209            case TokenTypes.LITERAL_DO:
210            case TokenTypes.LITERAL_IF:
211            case TokenTypes.STATIC_INIT:
212            case TokenTypes.LAMBDA:
213                startToken = ast;
214                brace = ast.findFirstToken(TokenTypes.SLIST);
215                break;
216            case TokenTypes.LITERAL_ELSE:
217                startToken = ast;
218                final DetailAST candidate = ast.getFirstChild();
219                brace = null;
220
221                if (candidate.getType() == TokenTypes.SLIST) {
222                    brace = candidate;
223                }
224                break;
225            default:
226                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
227                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
228                // It has been done to improve coverage to 100%. I couldn't replace it with
229                // if-else-if block because code was ugly and didn't pass pmd check.
230
231                startToken = ast;
232                brace = ast.findFirstToken(TokenTypes.LCURLY);
233                break;
234        }
235
236        if (brace != null) {
237            verifyBrace(brace, startToken);
238        }
239    }
240
241    /**
242     * Skip lines that only contain {@code TokenTypes.ANNOTATION}s.
243     * If the received {@code DetailAST}
244     * has annotations within its modifiers then first token on the line
245     * of the first token after all annotations is return. This might be
246     * an annotation.
247     * Otherwise, the received {@code DetailAST} is returned.
248     * @param ast {@code DetailAST}.
249     * @return {@code DetailAST}.
250     */
251    private static DetailAST skipAnnotationOnlyLines(DetailAST ast) {
252        DetailAST resultNode = ast;
253        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
254
255        if (modifiers != null) {
256            final DetailAST lastAnnotation = findLastAnnotation(modifiers);
257
258            if (lastAnnotation != null) {
259                final DetailAST tokenAfterLast;
260
261                if (lastAnnotation.getNextSibling() == null) {
262                    tokenAfterLast = modifiers.getNextSibling();
263                }
264                else {
265                    tokenAfterLast = lastAnnotation.getNextSibling();
266                }
267
268                if (tokenAfterLast.getLineNo() > lastAnnotation.getLineNo()) {
269                    resultNode = tokenAfterLast;
270                }
271                else {
272                    resultNode = getFirstAnnotationOnSameLine(lastAnnotation);
273                }
274            }
275        }
276        return resultNode;
277    }
278
279    /**
280     * Returns first annotation on same line.
281     * @param annotation
282     *            last annotation on the line
283     * @return first annotation on same line.
284     */
285    private static DetailAST getFirstAnnotationOnSameLine(DetailAST annotation) {
286        DetailAST previousAnnotation = annotation;
287        final int lastAnnotationLineNumber = previousAnnotation.getLineNo();
288        while (previousAnnotation.getPreviousSibling() != null
289                && previousAnnotation.getPreviousSibling().getLineNo()
290                    == lastAnnotationLineNumber) {
291
292            previousAnnotation = previousAnnotation.getPreviousSibling();
293        }
294        return previousAnnotation;
295    }
296
297    /**
298     * Find the last token of type {@code TokenTypes.ANNOTATION}
299     * under the given set of modifiers.
300     * @param modifiers {@code DetailAST}.
301     * @return {@code DetailAST} or null if there are no annotations.
302     */
303    private static DetailAST findLastAnnotation(DetailAST modifiers) {
304        DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
305        while (annotation != null && annotation.getNextSibling() != null
306               && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
307            annotation = annotation.getNextSibling();
308        }
309        return annotation;
310    }
311
312    /**
313     * Verifies that a specified left curly brace is placed correctly
314     * according to policy.
315     * @param brace token for left curly brace
316     * @param startToken token for start of expression
317     */
318    private void verifyBrace(final DetailAST brace,
319                             final DetailAST startToken) {
320        final String braceLine = getLine(brace.getLineNo() - 1);
321
322        // Check for being told to ignore, or have '{}' which is a special case
323        if (braceLine.length() <= brace.getColumnNo() + 1
324                || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
325            if (option == LeftCurlyOption.NL) {
326                if (!CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
327                    log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
328                }
329            }
330            else if (option == LeftCurlyOption.EOL) {
331
332                validateEol(brace, braceLine);
333            }
334            else if (startToken.getLineNo() != brace.getLineNo()) {
335
336                validateNewLinePosition(brace, startToken, braceLine);
337
338            }
339        }
340    }
341
342    /**
343     * Validate EOL case.
344     * @param brace brace AST
345     * @param braceLine line content
346     */
347    private void validateEol(DetailAST brace, String braceLine) {
348        if (CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
349            log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
350        }
351        if (!hasLineBreakAfter(brace)) {
352            log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
353        }
354    }
355
356    /**
357     * Validate token on new Line position.
358     * @param brace brace AST
359     * @param startToken start Token
360     * @param braceLine content of line with Brace
361     */
362    private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
363        // not on the same line
364        if (startToken.getLineNo() + 1 == brace.getLineNo()) {
365            if (CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
366                log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
367            }
368            else {
369                log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
370            }
371        }
372        else if (!CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
373            log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
374        }
375    }
376
377    /**
378     * Checks if left curly has line break after.
379     * @param leftCurly
380     *        Left curly token.
381     * @return
382     *        True, left curly has line break after.
383     */
384    private boolean hasLineBreakAfter(DetailAST leftCurly) {
385        DetailAST nextToken = null;
386        if (leftCurly.getType() == TokenTypes.SLIST) {
387            nextToken = leftCurly.getFirstChild();
388        }
389        else {
390            if (!ignoreEnums
391                    && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
392                nextToken = leftCurly.getNextSibling();
393            }
394        }
395        return nextToken == null
396                || nextToken.getType() == TokenTypes.RCURLY
397                || leftCurly.getLineNo() != nextToken.getLineNo();
398    }
399}