001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.blocks;
020
021import com.puppycrawl.tools.checkstyle.api.DetailAST;
022import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023import com.puppycrawl.tools.checkstyle.api.Utils;
024import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck;
025
026/**
027 * <p>
028 * Checks the placement of left curly braces on types, methods and
029 * other blocks:
030 *  {@link  TokenTypes#LITERAL_CATCH LITERAL_CATCH},  {@link
031 * TokenTypes#LITERAL_DO LITERAL_DO},  {@link TokenTypes#LITERAL_ELSE
032 * LITERAL_ELSE},  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},  {@link
033 * TokenTypes#LITERAL_FOR LITERAL_FOR},  {@link TokenTypes#LITERAL_IF
034 * LITERAL_IF},  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},  {@link
035 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},  {@link
036 * TokenTypes#LITERAL_TRY LITERAL_TRY},  {@link TokenTypes#LITERAL_WHILE
037 * LITERAL_WHILE}.
038 * </p>
039 *
040 * <p>
041 * The policy to verify is specified using the {@link LeftCurlyOption} class and
042 * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL}
043 * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength.
044 * The default value for maxLineLength is 80.
045 * </p>
046 * <p>
047 * An example of how to configure the check is:
048 * </p>
049 * <pre>
050 * &lt;module name="LeftCurly"/&gt;
051 * </pre>
052 * <p>
053 * An example of how to configure the check with policy
054 * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is:
055 * </p>
056 * <pre>
057 * &lt;module name="LeftCurly"&gt;
058 *      &lt;property name="option"
059 * value="nlow"/&gt;     &lt;property name="maxLineLength" value="120"/&gt; &lt;
060 * /module&gt;
061 * </pre>
062 * <p>
063 * An example of how to configure the check to validate enum definitions:
064 * </p>
065 * <pre>
066 * &lt;module name="LeftCurly"&gt;
067 *      &lt;property name="ignoreEnums" value="false"/&gt;
068 * &lt;/module&gt;
069 * </pre>
070 *
071 * @author Oliver Burn
072 * @author lkuehne
073 * @author maxvetrenko
074 * @version 1.0
075 */
076public class LeftCurlyCheck
077    extends AbstractOptionCheck<LeftCurlyOption>
078{
079    /** default maximum line length */
080    private static final int DEFAULT_MAX_LINE_LENGTH = 80;
081
082    /**
083     * A key is pointing to the warning message text in "messages.properties"
084     * file.
085     */
086    public static final String MSG_KEY_LINE_NEW = "line.new";
087
088    /**
089     * A key is pointing to the warning message text in "messages.properties"
090     * file.
091     */
092    public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
093
094    /**
095     * A key is pointing to the warning message text in "messages.properties"
096     * file.
097     */
098    public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
099
100    /** TODO: replace this ugly hack **/
101    private int maxLineLength = DEFAULT_MAX_LINE_LENGTH;
102
103    /** If true, Check will ignore enums*/
104    private boolean ignoreEnums = true;
105
106    /**
107     * Creates a default instance and sets the policy to EOL.
108     */
109    public LeftCurlyCheck()
110    {
111        super(LeftCurlyOption.EOL, LeftCurlyOption.class);
112    }
113
114    /**
115     * Sets the maximum line length used in calculating the placement of the
116     * left curly brace.
117     * @param maxLineLength the max allowed line length
118     */
119    public void setMaxLineLength(int maxLineLength)
120    {
121        this.maxLineLength = maxLineLength;
122    }
123
124    @Override
125    public int[] getDefaultTokens()
126    {
127        return new int[] {
128            TokenTypes.INTERFACE_DEF,
129            TokenTypes.CLASS_DEF,
130            TokenTypes.ANNOTATION_DEF,
131            TokenTypes.ENUM_DEF,
132            TokenTypes.CTOR_DEF,
133            TokenTypes.METHOD_DEF,
134            TokenTypes.ENUM_CONSTANT_DEF,
135            TokenTypes.LITERAL_WHILE,
136            TokenTypes.LITERAL_TRY,
137            TokenTypes.LITERAL_CATCH,
138            TokenTypes.LITERAL_FINALLY,
139            TokenTypes.LITERAL_SYNCHRONIZED,
140            TokenTypes.LITERAL_SWITCH,
141            TokenTypes.LITERAL_DO,
142            TokenTypes.LITERAL_IF,
143            TokenTypes.LITERAL_ELSE,
144            TokenTypes.LITERAL_FOR,
145            // TODO: need to handle....
146            //TokenTypes.STATIC_INIT,
147        };
148    }
149
150    @Override
151    public void visitToken(DetailAST ast)
152    {
153        final DetailAST startToken;
154        final DetailAST brace;
155
156        switch (ast.getType()) {
157            case TokenTypes.CTOR_DEF :
158            case TokenTypes.METHOD_DEF :
159                startToken = skipAnnotationOnlyLines(ast);
160                brace = ast.findFirstToken(TokenTypes.SLIST);
161                break;
162
163            case TokenTypes.INTERFACE_DEF :
164            case TokenTypes.CLASS_DEF :
165            case TokenTypes.ANNOTATION_DEF :
166            case TokenTypes.ENUM_DEF :
167            case TokenTypes.ENUM_CONSTANT_DEF :
168                startToken = skipAnnotationOnlyLines(ast);
169                final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
170                brace = (objBlock == null)
171                    ? null
172                    : objBlock.getFirstChild();
173                break;
174
175            case TokenTypes.LITERAL_WHILE:
176            case TokenTypes.LITERAL_CATCH:
177            case TokenTypes.LITERAL_SYNCHRONIZED:
178            case TokenTypes.LITERAL_FOR:
179            case TokenTypes.LITERAL_TRY:
180            case TokenTypes.LITERAL_FINALLY:
181            case TokenTypes.LITERAL_DO:
182            case TokenTypes.LITERAL_IF :
183                startToken = ast;
184                brace = ast.findFirstToken(TokenTypes.SLIST);
185                break;
186
187            case TokenTypes.LITERAL_ELSE :
188                startToken = ast;
189                final DetailAST candidate = ast.getFirstChild();
190                brace =
191                    (candidate.getType() == TokenTypes.SLIST)
192                    ? candidate
193                    : null; // silently ignore
194                break;
195
196            case TokenTypes.LITERAL_SWITCH :
197                startToken = ast;
198                brace = ast.findFirstToken(TokenTypes.LCURLY);
199                break;
200
201            default :
202                startToken = null;
203                brace = null;
204        }
205
206        if ((brace != null) && (startToken != null)) {
207            verifyBrace(brace, startToken);
208        }
209    }
210
211    /**
212     * Skip lines that only contain <code>TokenTypes.ANNOTATION</code>s.
213     * If the received <code>DetailAST</code>
214     * has annotations within its modifiers then first token on the line
215     * of the first token afer all annotations is return. This might be
216     * an annotation.
217     * Otherwise, the received <code>DetailAST</code> is returned.
218     * @param ast <code>DetailAST</code>.
219     * @return <code>DetailAST</code>.
220     */
221    private DetailAST skipAnnotationOnlyLines(DetailAST ast)
222    {
223        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
224        if (modifiers == null) {
225            return ast;
226        }
227        DetailAST lastAnnot = findLastAnnotation(modifiers);
228        if (lastAnnot == null) {
229            // There are no annotations.
230            return ast;
231        }
232        final DetailAST tokenAfterLast = lastAnnot.getNextSibling() != null
233                                       ? lastAnnot.getNextSibling()
234                                       : modifiers.getNextSibling();
235        if (tokenAfterLast.getLineNo() > lastAnnot.getLineNo()) {
236            return tokenAfterLast;
237        }
238        final int lastAnnotLineNumber = lastAnnot.getLineNo();
239        while (lastAnnot.getPreviousSibling() != null
240               && (lastAnnot.getPreviousSibling().getLineNo()
241                    == lastAnnotLineNumber))
242        {
243            lastAnnot = lastAnnot.getPreviousSibling();
244        }
245        return lastAnnot;
246    }
247
248    /**
249     * Find the last token of type <code>TokenTypes.ANNOTATION</code>
250     * under the given set of modifiers.
251     * @param modifiers <code>DetailAST</code>.
252     * @return <code>DetailAST</code> or null if there are no annotations.
253     */
254    private DetailAST findLastAnnotation(DetailAST modifiers)
255    {
256        DetailAST annot = modifiers.findFirstToken(TokenTypes.ANNOTATION);
257        while (annot != null && annot.getNextSibling() != null
258               && annot.getNextSibling().getType() == TokenTypes.ANNOTATION)
259        {
260            annot = annot.getNextSibling();
261        }
262        return annot;
263    }
264
265    /**
266     * Verifies that a specified left curly brace is placed correctly
267     * according to policy.
268     * @param brace token for left curly brace
269     * @param startToken token for start of expression
270     */
271    private void verifyBrace(final DetailAST brace,
272                             final DetailAST startToken)
273    {
274        final String braceLine = getLine(brace.getLineNo() - 1);
275
276        // calculate the previous line length without trailing whitespace. Need
277        // to handle the case where there is no previous line, cause the line
278        // being check is the first line in the file.
279        final int prevLineLen = (brace.getLineNo() == 1)
280            ? maxLineLength
281            : Utils.lengthMinusTrailingWhitespace(getLine(brace.getLineNo() - 2));
282
283        // Check for being told to ignore, or have '{}' which is a special case
284        if ((braceLine.length() > (brace.getColumnNo() + 1))
285            && (braceLine.charAt(brace.getColumnNo() + 1) == '}'))
286        {
287            ; // ignore
288        }
289        else if (getAbstractOption() == LeftCurlyOption.NL) {
290            if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
291                log(brace.getLineNo(), brace.getColumnNo(),
292                    MSG_KEY_LINE_NEW, "{");
293            }
294        }
295        else if (getAbstractOption() == LeftCurlyOption.EOL) {
296            if (Utils.whitespaceBefore(brace.getColumnNo(), braceLine)
297                && ((prevLineLen + 2) <= maxLineLength))
298            {
299                log(brace.getLineNo(), brace.getColumnNo(),
300                    MSG_KEY_LINE_PREVIOUS, "{");
301            }
302            if (!hasLineBreakAfter(brace)) {
303                log(brace.getLineNo(), brace.getColumnNo(), MSG_KEY_LINE_BREAK_AFTER);
304            }
305        }
306        else if (getAbstractOption() == LeftCurlyOption.NLOW) {
307            if (startToken.getLineNo() == brace.getLineNo()) {
308                ; // all ok as on the same line
309            }
310            else if ((startToken.getLineNo() + 1) == brace.getLineNo()) {
311                if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
312                    log(brace.getLineNo(), brace.getColumnNo(),
313                        MSG_KEY_LINE_NEW, "{");
314                }
315                else if ((prevLineLen + 2) <= maxLineLength) {
316                    log(brace.getLineNo(), brace.getColumnNo(),
317                        MSG_KEY_LINE_PREVIOUS, "{");
318                }
319            }
320            else if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
321                log(brace.getLineNo(), brace.getColumnNo(),
322                    MSG_KEY_LINE_NEW, "{");
323            }
324        }
325    }
326
327    /**
328     * Checks if left curly has line break after.
329     * @param leftCurly
330     *        Left curly token.
331     * @return
332     *        True, left curly has line break after.
333     */
334    private boolean hasLineBreakAfter(DetailAST leftCurly)
335    {
336        DetailAST nextToken = null;
337        if (leftCurly.getType() == TokenTypes.SLIST) {
338            nextToken = leftCurly.getFirstChild();
339        }
340        else {
341            if (leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF)
342            {
343                if (!ignoreEnums) {
344                    nextToken = leftCurly.getNextSibling();
345                }
346            }
347        }
348        if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) {
349            if (leftCurly.getLineNo() == nextToken.getLineNo()) {
350                return false;
351            }
352        }
353        return true;
354    }
355}