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 * <module name="LeftCurly"/>
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 * <module name="LeftCurly">
58 * <property name="option"
59 * value="nlow"/> <property name="maxLineLength" value="120"/> <
60 * /module>
61 * </pre>
62 * <p>
63 * An example of how to configure the check to validate enum definitions:
64 * </p>
65 * <pre>
66 * <module name="LeftCurly">
67 * <property name="ignoreEnums" value="false"/>
68 * </module>
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 }