View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2014  Oliver Burn
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 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 }