001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 the original author or authors. 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//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.whitespace; 021 022import java.util.ArrayList; 023import java.util.List; 024 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.FileContents; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030 031/** 032 * Checks for empty line separators after header, package, all import declarations, 033 * fields, constructors, methods, nested classes, 034 * static initializers and instance initializers. 035 * 036 * <p> By default the check will check the following statements: 037 * {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF}, 038 * {@link TokenTypes#IMPORT IMPORT}, 039 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 040 * {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF}, 041 * {@link TokenTypes#STATIC_INIT STATIC_INIT}, 042 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}, 043 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 044 * {@link TokenTypes#CTOR_DEF CTOR_DEF}, 045 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}. 046 * </p> 047 * 048 * <p> 049 * Example of declarations without empty line separator: 050 * </p> 051 * 052 * <pre> 053 * /////////////////////////////////////////////////// 054 * //HEADER 055 * /////////////////////////////////////////////////// 056 * package com.puppycrawl.tools.checkstyle.whitespace; 057 * import java.io.Serializable; 058 * class Foo 059 * { 060 * public static final int FOO_CONST = 1; 061 * public void foo() {} //should be separated from previous statement. 062 * } 063 * </pre> 064 * 065 * <p> An example of how to configure the check with default parameters is: 066 * </p> 067 * 068 * <pre> 069 * <module name="EmptyLineSeparator"/> 070 * </pre> 071 * 072 * <p> 073 * Example of declarations with empty line separator 074 * that is expected by the Check by default: 075 * </p> 076 * 077 * <pre> 078 * /////////////////////////////////////////////////// 079 * //HEADER 080 * /////////////////////////////////////////////////// 081 * 082 * package com.puppycrawl.tools.checkstyle.whitespace; 083 * 084 * import java.io.Serializable; 085 * 086 * class Foo 087 * { 088 * public static final int FOO_CONST = 1; 089 * 090 * public void foo() {} 091 * } 092 * </pre> 093 * <p> An example how to check empty line after 094 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and 095 * {@link TokenTypes#METHOD_DEF METHOD_DEF}: 096 * </p> 097 * 098 * <pre> 099 * <module name="EmptyLineSeparator"> 100 * <property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/> 101 * </module> 102 * </pre> 103 * 104 * <p> 105 * An example how to allow no empty line between fields: 106 * </p> 107 * <pre> 108 * <module name="EmptyLineSeparator"> 109 * <property name="allowNoEmptyLineBetweenFields" value="true"/> 110 * </module> 111 * </pre> 112 * 113 * <p> 114 * Example of declarations with multiple empty lines between class members (allowed by default): 115 * </p> 116 * 117 * <pre> 118 * /////////////////////////////////////////////////// 119 * //HEADER 120 * /////////////////////////////////////////////////// 121 * 122 * 123 * package com.puppycrawl.tools.checkstyle.whitespace; 124 * 125 * 126 * 127 * import java.io.Serializable; 128 * 129 * 130 * class Foo 131 * { 132 * public static final int FOO_CONST = 1; 133 * 134 * 135 * 136 * public void foo() {} 137 * } 138 * </pre> 139 * <p> 140 * An example how to disallow multiple empty lines between class members: 141 * </p> 142 * <pre> 143 * <module name="EmptyLineSeparator"> 144 * <property name="allowMultipleEmptyLines" value="false"/> 145 * </module> 146 * </pre> 147 * 148 * <p> 149 * An example how to disallow multiple empty line inside methods, constructors, etc.: 150 * </p> 151 * <pre> 152 * <module name="EmptyLineSeparator"> 153 * <property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/> 154 * </module> 155 * </pre> 156 * 157 * <p> The check is valid only for statements that have body: 158 * {@link TokenTypes#CLASS_DEF}, 159 * {@link TokenTypes#INTERFACE_DEF}, 160 * {@link TokenTypes#ENUM_DEF}, 161 * {@link TokenTypes#STATIC_INIT}, 162 * {@link TokenTypes#INSTANCE_INIT}, 163 * {@link TokenTypes#METHOD_DEF}, 164 * {@link TokenTypes#CTOR_DEF} 165 * </p> 166 * <p> 167 * Example of declarations with multiple empty lines inside method: 168 * </p> 169 * 170 * <pre> 171 * /////////////////////////////////////////////////// 172 * //HEADER 173 * /////////////////////////////////////////////////// 174 * 175 * package com.puppycrawl.tools.checkstyle.whitespace; 176 * 177 * class Foo 178 * { 179 * 180 * public void foo() { 181 * 182 * 183 * System.out.println(1); // violation since method has 2 empty lines subsequently 184 * } 185 * } 186 * </pre> 187 * @author maxvetrenko 188 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 189 */ 190public class EmptyLineSeparatorCheck extends AbstractCheck { 191 192 /** 193 * A key is pointing to the warning message empty.line.separator in "messages.properties" 194 * file. 195 */ 196 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator"; 197 198 /** 199 * A key is pointing to the warning message empty.line.separator.multiple.lines 200 * in "messages.properties" 201 * file. 202 */ 203 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines"; 204 205 /** 206 * A key is pointing to the warning message empty.line.separator.lines.after 207 * in "messages.properties" file. 208 */ 209 public static final String MSG_MULTIPLE_LINES_AFTER = 210 "empty.line.separator.multiple.lines.after"; 211 212 /** 213 * A key is pointing to the warning message empty.line.separator.multiple.lines.inside 214 * in "messages.properties" file. 215 */ 216 public static final String MSG_MULTIPLE_LINES_INSIDE = 217 "empty.line.separator.multiple.lines.inside"; 218 219 /** Allows no empty line between fields. */ 220 private boolean allowNoEmptyLineBetweenFields; 221 222 /** Allows multiple empty lines between class members. */ 223 private boolean allowMultipleEmptyLines = true; 224 225 /** Allows multiple empty lines inside class members. */ 226 private boolean allowMultipleEmptyLinesInsideClassMembers = true; 227 228 /** 229 * Allow no empty line between fields. 230 * @param allow 231 * User's value. 232 */ 233 public final void setAllowNoEmptyLineBetweenFields(boolean allow) { 234 allowNoEmptyLineBetweenFields = allow; 235 } 236 237 /** 238 * Allow multiple empty lines between class members. 239 * @param allow User's value. 240 */ 241 public void setAllowMultipleEmptyLines(boolean allow) { 242 allowMultipleEmptyLines = allow; 243 } 244 245 /** 246 * Allow multiple empty lines inside class members. 247 * @param allow User's value. 248 */ 249 public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) { 250 allowMultipleEmptyLinesInsideClassMembers = allow; 251 } 252 253 @Override 254 public int[] getDefaultTokens() { 255 return getAcceptableTokens(); 256 } 257 258 @Override 259 public int[] getAcceptableTokens() { 260 return new int[] { 261 TokenTypes.PACKAGE_DEF, 262 TokenTypes.IMPORT, 263 TokenTypes.CLASS_DEF, 264 TokenTypes.INTERFACE_DEF, 265 TokenTypes.ENUM_DEF, 266 TokenTypes.STATIC_INIT, 267 TokenTypes.INSTANCE_INIT, 268 TokenTypes.METHOD_DEF, 269 TokenTypes.CTOR_DEF, 270 TokenTypes.VARIABLE_DEF, 271 }; 272 } 273 274 @Override 275 public int[] getRequiredTokens() { 276 return CommonUtils.EMPTY_INT_ARRAY; 277 } 278 279 @Override 280 public void visitToken(DetailAST ast) { 281 if (hasMultipleLinesBefore(ast)) { 282 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 283 } 284 if (!allowMultipleEmptyLinesInsideClassMembers) { 285 processMultipleLinesInside(ast); 286 } 287 288 final DetailAST nextToken = ast.getNextSibling(); 289 if (nextToken != null) { 290 final int astType = ast.getType(); 291 switch (astType) { 292 case TokenTypes.VARIABLE_DEF: 293 processVariableDef(ast, nextToken); 294 break; 295 case TokenTypes.IMPORT: 296 processImport(ast, nextToken, astType); 297 break; 298 case TokenTypes.PACKAGE_DEF: 299 processPackage(ast, nextToken); 300 break; 301 default: 302 if (nextToken.getType() == TokenTypes.RCURLY) { 303 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) { 304 log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText()); 305 } 306 } 307 else if (!hasEmptyLineAfter(ast)) { 308 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 309 nextToken.getText()); 310 } 311 } 312 } 313 } 314 315 /** 316 * Log violation in case there are multiple empty lines inside constructor, 317 * initialization block or method. 318 * @param ast the ast to check. 319 */ 320 private void processMultipleLinesInside(DetailAST ast) { 321 final int astType = ast.getType(); 322 if (isClassMemberBlock(astType)) { 323 final List<Integer> emptyLines = getEmptyLines(ast); 324 final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines); 325 326 for (Integer lineNo : emptyLinesToLog) { 327 // Checkstyle counts line numbers from 0 but IDE from 1 328 log(lineNo + 1, MSG_MULTIPLE_LINES_INSIDE); 329 } 330 } 331 } 332 333 /** 334 * Whether the AST is a class member block. 335 * @param astType the AST to check. 336 * @return true if the AST is a class member block. 337 */ 338 private static boolean isClassMemberBlock(int astType) { 339 return astType == TokenTypes.STATIC_INIT 340 || astType == TokenTypes.INSTANCE_INIT 341 || astType == TokenTypes.METHOD_DEF 342 || astType == TokenTypes.CTOR_DEF; 343 } 344 345 /** 346 * Get list of empty lines. 347 * @param ast the ast to check. 348 * @return list of line numbers for empty lines. 349 */ 350 private List<Integer> getEmptyLines(DetailAST ast) { 351 final DetailAST lastToken = ast.getLastChild().getLastChild(); 352 int lastTokenLineNo = 0; 353 if (lastToken != null) { 354 lastTokenLineNo = lastToken.getLineNo(); 355 } 356 final List<Integer> emptyLines = new ArrayList<>(); 357 final FileContents fileContents = getFileContents(); 358 359 for (int lineNo = ast.getLineNo(); lineNo < lastTokenLineNo; lineNo++) { 360 if (fileContents.lineIsBlank(lineNo)) { 361 emptyLines.add(lineNo); 362 } 363 } 364 return emptyLines; 365 } 366 367 /** 368 * Get list of empty lines to log. 369 * @param emptyLines list of empty lines. 370 * @return list of empty lines to log. 371 */ 372 private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) { 373 final List<Integer> emptyLinesToLog = new ArrayList<>(); 374 if (emptyLines.size() > 1) { 375 int previousEmptyLineNo = emptyLines.get(0); 376 for (int emptyLineNo : emptyLines) { 377 if (previousEmptyLineNo + 1 == emptyLineNo) { 378 emptyLinesToLog.add(emptyLineNo); 379 } 380 previousEmptyLineNo = emptyLineNo; 381 } 382 } 383 return emptyLinesToLog; 384 } 385 386 /** 387 * Whether the token has not allowed multiple empty lines before. 388 * @param ast the ast to check. 389 * @return true if the token has not allowed multiple empty lines before. 390 */ 391 private boolean hasMultipleLinesBefore(DetailAST ast) { 392 boolean result = false; 393 if ((ast.getType() != TokenTypes.VARIABLE_DEF 394 || isTypeField(ast)) 395 && hasNotAllowedTwoEmptyLinesBefore(ast)) { 396 result = true; 397 } 398 return result; 399 } 400 401 /** 402 * Process Package. 403 * @param ast token 404 * @param nextToken next token 405 */ 406 private void processPackage(DetailAST ast, DetailAST nextToken) { 407 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 408 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 409 } 410 if (!hasEmptyLineAfter(ast)) { 411 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 412 } 413 } 414 415 /** 416 * Process Import. 417 * @param ast token 418 * @param nextToken next token 419 * @param astType token Type 420 */ 421 private void processImport(DetailAST ast, DetailAST nextToken, int astType) { 422 if (astType != nextToken.getType() && !hasEmptyLineAfter(ast)) { 423 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 424 } 425 } 426 427 /** 428 * Process Variable. 429 * @param ast token 430 * @param nextToken next Token 431 */ 432 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 433 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 434 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 435 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 436 nextToken.getText()); 437 } 438 } 439 440 /** 441 * Checks whether token placement violates policy of empty line between fields. 442 * @param detailAST token to be analyzed 443 * @return true if policy is violated and warning should be raised; false otherwise 444 */ 445 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 446 return allowNoEmptyLineBetweenFields 447 && detailAST.getType() != TokenTypes.VARIABLE_DEF 448 && detailAST.getType() != TokenTypes.RCURLY 449 || !allowNoEmptyLineBetweenFields 450 && detailAST.getType() != TokenTypes.RCURLY; 451 } 452 453 /** 454 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 455 * @param token DetailAST token 456 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 457 */ 458 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 459 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 460 && isPrePreviousLineEmpty(token); 461 } 462 463 /** 464 * Checks if a token has empty pre-previous line. 465 * @param token DetailAST token. 466 * @return true, if token has empty lines before. 467 */ 468 private boolean isPrePreviousLineEmpty(DetailAST token) { 469 boolean result = false; 470 final int lineNo = token.getLineNo(); 471 // 3 is the number of the pre-previous line because the numbering starts from zero. 472 final int number = 3; 473 if (lineNo >= number) { 474 final String prePreviousLine = getLines()[lineNo - number]; 475 result = prePreviousLine.trim().isEmpty(); 476 } 477 return result; 478 } 479 480 /** 481 * Checks if token have empty line after. 482 * @param token token. 483 * @return true if token have empty line after. 484 */ 485 private boolean hasEmptyLineAfter(DetailAST token) { 486 DetailAST lastToken = token.getLastChild().getLastChild(); 487 if (lastToken == null) { 488 lastToken = token.getLastChild(); 489 } 490 // Start of the next token 491 final int nextBegin = token.getNextSibling().getLineNo(); 492 // End of current token. 493 final int currentEnd = lastToken.getLineNo(); 494 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 495 } 496 497 /** 498 * Checks, whether there are empty lines within the specified line range. Line numbering is 499 * started from 1 for parameter values 500 * @param startLine number of the first line in the range 501 * @param endLine number of the second line in the range 502 * @return <code>true</code> if found any blank line within the range, <code>false</code> 503 * otherwise 504 */ 505 private boolean hasEmptyLine(int startLine, int endLine) { 506 // Initial value is false - blank line not found 507 boolean result = false; 508 if (startLine <= endLine) { 509 final FileContents fileContents = getFileContents(); 510 for (int line = startLine; line <= endLine; line++) { 511 // Check, if the line is blank. Lines are numbered from 0, so subtract 1 512 if (fileContents.lineIsBlank(line - 1)) { 513 result = true; 514 break; 515 } 516 } 517 } 518 return result; 519 } 520 521 /** 522 * Checks if a token has a empty line before. 523 * @param token token. 524 * @return true, if token have empty line before. 525 */ 526 private boolean hasEmptyLineBefore(DetailAST token) { 527 final int lineNo = token.getLineNo(); 528 if (lineNo == 1) { 529 return false; 530 } 531 // [lineNo - 2] is the number of the previous line because the numbering starts from zero. 532 final String lineBefore = getLines()[lineNo - 2]; 533 return lineBefore.trim().isEmpty(); 534 } 535 536 /** 537 * If variable definition is a type field. 538 * @param variableDef variable definition. 539 * @return true variable definition is a type field. 540 */ 541 private static boolean isTypeField(DetailAST variableDef) { 542 final int parentType = variableDef.getParent().getParent().getType(); 543 return parentType == TokenTypes.CLASS_DEF; 544 } 545}