View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2015 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  package com.puppycrawl.tools.checkstyle.checks.blocks;
20  
21  import com.puppycrawl.tools.checkstyle.api.DetailAST;
22  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
23  import com.puppycrawl.tools.checkstyle.api.Utils;
24  import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck;
25  
26  /**
27   * <p>
28   * Checks the placement of left curly braces on types, methods and
29   * other blocks:
30   *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},  {@link
31   * TokenTypes#LITERAL_DO LITERAL_DO},  {@link TokenTypes#LITERAL_ELSE
32   * LITERAL_ELSE},  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},  {@link
33   * TokenTypes#LITERAL_FOR LITERAL_FOR},  {@link TokenTypes#LITERAL_IF
34   * LITERAL_IF},  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},  {@link
35   * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},  {@link
36   * TokenTypes#LITERAL_TRY LITERAL_TRY},  {@link TokenTypes#LITERAL_WHILE
37   * LITERAL_WHILE}.
38   * </p>
39   *
40   * <p>
41   * The policy to verify is specified using the {@link LeftCurlyOption} class and
42   * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL}
43   * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength.
44   * The default value for maxLineLength is 80.
45   * </p>
46   * <p>
47   * An example of how to configure the check is:
48   * </p>
49   * <pre>
50   * &lt;module name="LeftCurly"/&gt;
51   * </pre>
52   * <p>
53   * An example of how to configure the check with policy
54   * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is:
55   * </p>
56   * <pre>
57   * &lt;module name="LeftCurly"&gt;
58   *      &lt;property name="option"
59   * value="nlow"/&gt;     &lt;property name="maxLineLength" value="120"/&gt; &lt;
60   * /module&gt;
61   * </pre>
62   * <p>
63   * An example of how to configure the check to validate enum definitions:
64   * </p>
65   * <pre>
66   * &lt;module name="LeftCurly"&gt;
67   *      &lt;property name="ignoreEnums" value="false"/&gt;
68   * &lt;/module&gt;
69   * </pre>
70   *
71   * @author Oliver Burn
72   * @author lkuehne
73   * @author maxvetrenko
74   */
75  public class LeftCurlyCheck
76      extends AbstractOptionCheck<LeftCurlyOption>
77  {
78      /** default maximum line length */
79      private static final int DEFAULT_MAX_LINE_LENGTH = 80;
80  
81      /**
82       * A key is pointing to the warning message text in "messages.properties"
83       * file.
84       */
85      public static final String MSG_KEY_LINE_NEW = "line.new";
86  
87      /**
88       * A key is pointing to the warning message text in "messages.properties"
89       * file.
90       */
91      public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
92  
93      /**
94       * A key is pointing to the warning message text in "messages.properties"
95       * file.
96       */
97      public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
98  
99      /** TODO: replace this ugly hack **/
100     private int maxLineLength = DEFAULT_MAX_LINE_LENGTH;
101 
102     /** If true, Check will ignore enums*/
103     private boolean ignoreEnums = true;
104 
105     /**
106      * Creates a default instance and sets the policy to EOL.
107      */
108     public LeftCurlyCheck()
109     {
110         super(LeftCurlyOption.EOL, LeftCurlyOption.class);
111     }
112 
113     /**
114      * Sets the maximum line length used in calculating the placement of the
115      * left curly brace.
116      * @param maxLineLength the max allowed line length
117      */
118     public void setMaxLineLength(int maxLineLength)
119     {
120         this.maxLineLength = maxLineLength;
121     }
122 
123     @Override
124     public int[] getDefaultTokens()
125     {
126         return new int[] {
127             TokenTypes.INTERFACE_DEF,
128             TokenTypes.CLASS_DEF,
129             TokenTypes.ANNOTATION_DEF,
130             TokenTypes.ENUM_DEF,
131             TokenTypes.CTOR_DEF,
132             TokenTypes.METHOD_DEF,
133             TokenTypes.ENUM_CONSTANT_DEF,
134             TokenTypes.LITERAL_WHILE,
135             TokenTypes.LITERAL_TRY,
136             TokenTypes.LITERAL_CATCH,
137             TokenTypes.LITERAL_FINALLY,
138             TokenTypes.LITERAL_SYNCHRONIZED,
139             TokenTypes.LITERAL_SWITCH,
140             TokenTypes.LITERAL_DO,
141             TokenTypes.LITERAL_IF,
142             TokenTypes.LITERAL_ELSE,
143             TokenTypes.LITERAL_FOR,
144             // TODO: need to handle....
145             //TokenTypes.STATIC_INIT,
146         };
147     }
148 
149     @Override
150     public int[] getAcceptableTokens()
151     {
152         return new int[] {
153             TokenTypes.INTERFACE_DEF,
154             TokenTypes.CLASS_DEF,
155             TokenTypes.ANNOTATION_DEF,
156             TokenTypes.ENUM_DEF,
157             TokenTypes.CTOR_DEF,
158             TokenTypes.METHOD_DEF,
159             TokenTypes.ENUM_CONSTANT_DEF,
160             TokenTypes.LITERAL_WHILE,
161             TokenTypes.LITERAL_TRY,
162             TokenTypes.LITERAL_CATCH,
163             TokenTypes.LITERAL_FINALLY,
164             TokenTypes.LITERAL_SYNCHRONIZED,
165             TokenTypes.LITERAL_SWITCH,
166             TokenTypes.LITERAL_DO,
167             TokenTypes.LITERAL_IF,
168             TokenTypes.LITERAL_ELSE,
169             TokenTypes.LITERAL_FOR,
170         };
171     }
172 
173     @Override
174     public void visitToken(DetailAST ast)
175     {
176         final DetailAST startToken;
177         final DetailAST brace;
178 
179         switch (ast.getType()) {
180             case TokenTypes.CTOR_DEF :
181             case TokenTypes.METHOD_DEF :
182                 startToken = skipAnnotationOnlyLines(ast);
183                 brace = ast.findFirstToken(TokenTypes.SLIST);
184                 break;
185 
186             case TokenTypes.INTERFACE_DEF :
187             case TokenTypes.CLASS_DEF :
188             case TokenTypes.ANNOTATION_DEF :
189             case TokenTypes.ENUM_DEF :
190             case TokenTypes.ENUM_CONSTANT_DEF :
191                 startToken = skipAnnotationOnlyLines(ast);
192                 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
193                 brace = objBlock == null
194                     ? null
195                     : objBlock.getFirstChild();
196                 break;
197 
198             case TokenTypes.LITERAL_WHILE:
199             case TokenTypes.LITERAL_CATCH:
200             case TokenTypes.LITERAL_SYNCHRONIZED:
201             case TokenTypes.LITERAL_FOR:
202             case TokenTypes.LITERAL_TRY:
203             case TokenTypes.LITERAL_FINALLY:
204             case TokenTypes.LITERAL_DO:
205             case TokenTypes.LITERAL_IF :
206                 startToken = ast;
207                 brace = ast.findFirstToken(TokenTypes.SLIST);
208                 break;
209 
210             case TokenTypes.LITERAL_ELSE :
211                 startToken = ast;
212                 final DetailAST candidate = ast.getFirstChild();
213                 brace =
214                     candidate.getType() == TokenTypes.SLIST
215                     ? candidate
216                     : null; // silently ignore
217                 break;
218 
219             case TokenTypes.LITERAL_SWITCH :
220                 startToken = ast;
221                 brace = ast.findFirstToken(TokenTypes.LCURLY);
222                 break;
223 
224             default :
225                 startToken = null;
226                 brace = null;
227         }
228 
229         if (brace != null && startToken != null) {
230             verifyBrace(brace, startToken);
231         }
232     }
233 
234     /**
235      * Skip lines that only contain <code>TokenTypes.ANNOTATION</code>s.
236      * If the received <code>DetailAST</code>
237      * has annotations within its modifiers then first token on the line
238      * of the first token afer all annotations is return. This might be
239      * an annotation.
240      * Otherwise, the received <code>DetailAST</code> is returned.
241      * @param ast <code>DetailAST</code>.
242      * @return <code>DetailAST</code>.
243      */
244     private DetailAST skipAnnotationOnlyLines(DetailAST ast)
245     {
246         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
247         if (modifiers == null) {
248             return ast;
249         }
250         DetailAST lastAnnot = findLastAnnotation(modifiers);
251         if (lastAnnot == null) {
252             // There are no annotations.
253             return ast;
254         }
255         final DetailAST tokenAfterLast = lastAnnot.getNextSibling() != null
256                                        ? lastAnnot.getNextSibling()
257                                        : modifiers.getNextSibling();
258         if (tokenAfterLast.getLineNo() > lastAnnot.getLineNo()) {
259             return tokenAfterLast;
260         }
261         final int lastAnnotLineNumber = lastAnnot.getLineNo();
262         while (lastAnnot.getPreviousSibling() != null
263                && lastAnnot.getPreviousSibling().getLineNo()
264                     == lastAnnotLineNumber)
265         {
266             lastAnnot = lastAnnot.getPreviousSibling();
267         }
268         return lastAnnot;
269     }
270 
271     /**
272      * Find the last token of type <code>TokenTypes.ANNOTATION</code>
273      * under the given set of modifiers.
274      * @param modifiers <code>DetailAST</code>.
275      * @return <code>DetailAST</code> or null if there are no annotations.
276      */
277     private DetailAST findLastAnnotation(DetailAST modifiers)
278     {
279         DetailAST annot = modifiers.findFirstToken(TokenTypes.ANNOTATION);
280         while (annot != null && annot.getNextSibling() != null
281                && annot.getNextSibling().getType() == TokenTypes.ANNOTATION)
282         {
283             annot = annot.getNextSibling();
284         }
285         return annot;
286     }
287 
288     /**
289      * Verifies that a specified left curly brace is placed correctly
290      * according to policy.
291      * @param brace token for left curly brace
292      * @param startToken token for start of expression
293      */
294     private void verifyBrace(final DetailAST brace,
295                              final DetailAST startToken)
296     {
297         final String braceLine = getLine(brace.getLineNo() - 1);
298 
299         // calculate the previous line length without trailing whitespace. Need
300         // to handle the case where there is no previous line, cause the line
301         // being check is the first line in the file.
302         final int prevLineLen = brace.getLineNo() == 1
303             ? maxLineLength
304             : Utils.lengthMinusTrailingWhitespace(getLine(brace.getLineNo() - 2));
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         {
310             // ignore
311         }
312         else if (getAbstractOption() == LeftCurlyOption.NL) {
313             if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
314                 log(brace.getLineNo(), brace.getColumnNo(),
315                     MSG_KEY_LINE_NEW, "{");
316             }
317         }
318         else if (getAbstractOption() == LeftCurlyOption.EOL) {
319             if (Utils.whitespaceBefore(brace.getColumnNo(), braceLine)
320                 && prevLineLen + 2 <= maxLineLength)
321             {
322                 log(brace.getLineNo(), brace.getColumnNo(),
323                     MSG_KEY_LINE_PREVIOUS, "{");
324             }
325             if (!hasLineBreakAfter(brace)) {
326                 log(brace.getLineNo(), brace.getColumnNo(), MSG_KEY_LINE_BREAK_AFTER);
327             }
328         }
329         else if (getAbstractOption() == LeftCurlyOption.NLOW) {
330             if (startToken.getLineNo() == brace.getLineNo()) {
331                 // all ok as on the same line
332             }
333             else if (startToken.getLineNo() + 1 == brace.getLineNo()) {
334                 if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
335                     log(brace.getLineNo(), brace.getColumnNo(),
336                         MSG_KEY_LINE_NEW, "{");
337                 }
338                 else if (prevLineLen + 2 <= maxLineLength) {
339                     log(brace.getLineNo(), brace.getColumnNo(),
340                         MSG_KEY_LINE_PREVIOUS, "{");
341                 }
342             }
343             else if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
344                 log(brace.getLineNo(), brace.getColumnNo(),
345                     MSG_KEY_LINE_NEW, "{");
346             }
347         }
348     }
349 
350     /**
351      * Checks if left curly has line break after.
352      * @param leftCurly
353      *        Left curly token.
354      * @return
355      *        True, left curly has line break after.
356      */
357     private boolean hasLineBreakAfter(DetailAST leftCurly)
358     {
359         DetailAST nextToken = null;
360         if (leftCurly.getType() == TokenTypes.SLIST) {
361             nextToken = leftCurly.getFirstChild();
362         }
363         else {
364             if (leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF)
365             {
366                 if (!ignoreEnums) {
367                     nextToken = leftCurly.getNextSibling();
368                 }
369             }
370         }
371         if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) {
372             if (leftCurly.getLineNo() == nextToken.getLineNo()) {
373                 return false;
374             }
375         }
376         return true;
377     }
378 }