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