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   * @version 1.0
75   */
76  public class LeftCurlyCheck
77      extends AbstractOptionCheck<LeftCurlyOption>
78  {
79      /** default maximum line length */
80      private static final int DEFAULT_MAX_LINE_LENGTH = 80;
81  
82      /**
83       * A key is pointing to the warning message text in "messages.properties"
84       * file.
85       */
86      public static final String MSG_KEY_LINE_NEW = "line.new";
87  
88      /**
89       * A key is pointing to the warning message text in "messages.properties"
90       * file.
91       */
92      public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
93  
94      /**
95       * A key is pointing to the warning message text in "messages.properties"
96       * file.
97       */
98      public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
99  
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 int[] getAcceptableTokens()
152     {
153         return new int[] {
154             TokenTypes.INTERFACE_DEF,
155             TokenTypes.CLASS_DEF,
156             TokenTypes.ANNOTATION_DEF,
157             TokenTypes.ENUM_DEF,
158             TokenTypes.CTOR_DEF,
159             TokenTypes.METHOD_DEF,
160             TokenTypes.ENUM_CONSTANT_DEF,
161             TokenTypes.LITERAL_WHILE,
162             TokenTypes.LITERAL_TRY,
163             TokenTypes.LITERAL_CATCH,
164             TokenTypes.LITERAL_FINALLY,
165             TokenTypes.LITERAL_SYNCHRONIZED,
166             TokenTypes.LITERAL_SWITCH,
167             TokenTypes.LITERAL_DO,
168             TokenTypes.LITERAL_IF,
169             TokenTypes.LITERAL_ELSE,
170             TokenTypes.LITERAL_FOR,
171         };
172     }
173 
174     @Override
175     public void visitToken(DetailAST ast)
176     {
177         final DetailAST startToken;
178         final DetailAST brace;
179 
180         switch (ast.getType()) {
181             case TokenTypes.CTOR_DEF :
182             case TokenTypes.METHOD_DEF :
183                 startToken = skipAnnotationOnlyLines(ast);
184                 brace = ast.findFirstToken(TokenTypes.SLIST);
185                 break;
186 
187             case TokenTypes.INTERFACE_DEF :
188             case TokenTypes.CLASS_DEF :
189             case TokenTypes.ANNOTATION_DEF :
190             case TokenTypes.ENUM_DEF :
191             case TokenTypes.ENUM_CONSTANT_DEF :
192                 startToken = skipAnnotationOnlyLines(ast);
193                 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
194                 brace = (objBlock == null)
195                     ? null
196                     : objBlock.getFirstChild();
197                 break;
198 
199             case TokenTypes.LITERAL_WHILE:
200             case TokenTypes.LITERAL_CATCH:
201             case TokenTypes.LITERAL_SYNCHRONIZED:
202             case TokenTypes.LITERAL_FOR:
203             case TokenTypes.LITERAL_TRY:
204             case TokenTypes.LITERAL_FINALLY:
205             case TokenTypes.LITERAL_DO:
206             case TokenTypes.LITERAL_IF :
207                 startToken = ast;
208                 brace = ast.findFirstToken(TokenTypes.SLIST);
209                 break;
210 
211             case TokenTypes.LITERAL_ELSE :
212                 startToken = ast;
213                 final DetailAST candidate = ast.getFirstChild();
214                 brace =
215                     (candidate.getType() == TokenTypes.SLIST)
216                     ? candidate
217                     : null; // silently ignore
218                 break;
219 
220             case TokenTypes.LITERAL_SWITCH :
221                 startToken = ast;
222                 brace = ast.findFirstToken(TokenTypes.LCURLY);
223                 break;
224 
225             default :
226                 startToken = null;
227                 brace = null;
228         }
229 
230         if ((brace != null) && (startToken != null)) {
231             verifyBrace(brace, startToken);
232         }
233     }
234 
235     /**
236      * Skip lines that only contain <code>TokenTypes.ANNOTATION</code>s.
237      * If the received <code>DetailAST</code>
238      * has annotations within its modifiers then first token on the line
239      * of the first token afer all annotations is return. This might be
240      * an annotation.
241      * Otherwise, the received <code>DetailAST</code> is returned.
242      * @param ast <code>DetailAST</code>.
243      * @return <code>DetailAST</code>.
244      */
245     private DetailAST skipAnnotationOnlyLines(DetailAST ast)
246     {
247         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
248         if (modifiers == null) {
249             return ast;
250         }
251         DetailAST lastAnnot = findLastAnnotation(modifiers);
252         if (lastAnnot == null) {
253             // There are no annotations.
254             return ast;
255         }
256         final DetailAST tokenAfterLast = lastAnnot.getNextSibling() != null
257                                        ? lastAnnot.getNextSibling()
258                                        : modifiers.getNextSibling();
259         if (tokenAfterLast.getLineNo() > lastAnnot.getLineNo()) {
260             return tokenAfterLast;
261         }
262         final int lastAnnotLineNumber = lastAnnot.getLineNo();
263         while (lastAnnot.getPreviousSibling() != null
264                && (lastAnnot.getPreviousSibling().getLineNo()
265                     == lastAnnotLineNumber))
266         {
267             lastAnnot = lastAnnot.getPreviousSibling();
268         }
269         return lastAnnot;
270     }
271 
272     /**
273      * Find the last token of type <code>TokenTypes.ANNOTATION</code>
274      * under the given set of modifiers.
275      * @param modifiers <code>DetailAST</code>.
276      * @return <code>DetailAST</code> or null if there are no annotations.
277      */
278     private DetailAST findLastAnnotation(DetailAST modifiers)
279     {
280         DetailAST annot = modifiers.findFirstToken(TokenTypes.ANNOTATION);
281         while (annot != null && annot.getNextSibling() != null
282                && annot.getNextSibling().getType() == TokenTypes.ANNOTATION)
283         {
284             annot = annot.getNextSibling();
285         }
286         return annot;
287     }
288 
289     /**
290      * Verifies that a specified left curly brace is placed correctly
291      * according to policy.
292      * @param brace token for left curly brace
293      * @param startToken token for start of expression
294      */
295     private void verifyBrace(final DetailAST brace,
296                              final DetailAST startToken)
297     {
298         final String braceLine = getLine(brace.getLineNo() - 1);
299 
300         // calculate the previous line length without trailing whitespace. Need
301         // to handle the case where there is no previous line, cause the line
302         // being check is the first line in the file.
303         final int prevLineLen = (brace.getLineNo() == 1)
304             ? maxLineLength
305             : Utils.lengthMinusTrailingWhitespace(getLine(brace.getLineNo() - 2));
306 
307         // Check for being told to ignore, or have '{}' which is a special case
308         if ((braceLine.length() > (brace.getColumnNo() + 1))
309             && (braceLine.charAt(brace.getColumnNo() + 1) == '}'))
310         {
311             ; // ignore
312         }
313         else if (getAbstractOption() == LeftCurlyOption.NL) {
314             if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
315                 log(brace.getLineNo(), brace.getColumnNo(),
316                     MSG_KEY_LINE_NEW, "{");
317             }
318         }
319         else if (getAbstractOption() == LeftCurlyOption.EOL) {
320             if (Utils.whitespaceBefore(brace.getColumnNo(), braceLine)
321                 && ((prevLineLen + 2) <= maxLineLength))
322             {
323                 log(brace.getLineNo(), brace.getColumnNo(),
324                     MSG_KEY_LINE_PREVIOUS, "{");
325             }
326             if (!hasLineBreakAfter(brace)) {
327                 log(brace.getLineNo(), brace.getColumnNo(), MSG_KEY_LINE_BREAK_AFTER);
328             }
329         }
330         else if (getAbstractOption() == LeftCurlyOption.NLOW) {
331             if (startToken.getLineNo() == brace.getLineNo()) {
332                 ; // all ok as on the same line
333             }
334             else if ((startToken.getLineNo() + 1) == brace.getLineNo()) {
335                 if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
336                     log(brace.getLineNo(), brace.getColumnNo(),
337                         MSG_KEY_LINE_NEW, "{");
338                 }
339                 else if ((prevLineLen + 2) <= maxLineLength) {
340                     log(brace.getLineNo(), brace.getColumnNo(),
341                         MSG_KEY_LINE_PREVIOUS, "{");
342                 }
343             }
344             else if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) {
345                 log(brace.getLineNo(), brace.getColumnNo(),
346                     MSG_KEY_LINE_NEW, "{");
347             }
348         }
349     }
350 
351     /**
352      * Checks if left curly has line break after.
353      * @param leftCurly
354      *        Left curly token.
355      * @return
356      *        True, left curly has line break after.
357      */
358     private boolean hasLineBreakAfter(DetailAST leftCurly)
359     {
360         DetailAST nextToken = null;
361         if (leftCurly.getType() == TokenTypes.SLIST) {
362             nextToken = leftCurly.getFirstChild();
363         }
364         else {
365             if (leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF)
366             {
367                 if (!ignoreEnums) {
368                     nextToken = leftCurly.getNextSibling();
369                 }
370             }
371         }
372         if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) {
373             if (leftCurly.getLineNo() == nextToken.getLineNo()) {
374                 return false;
375             }
376         }
377         return true;
378     }
379 }