001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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 org.apache.commons.lang3.ArrayUtils; 023 024import com.puppycrawl.tools.checkstyle.api.Check; 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.FileContents; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * Checks for empty line separators after header, package, all import declarations, 031 * fields, constructors, methods, nested classes, 032 * static initializers and instance initializers. 033 * 034 * <p> By default the check will check the following statements: 035 * {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF}, 036 * {@link TokenTypes#IMPORT IMPORT}, 037 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 038 * {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF}, 039 * {@link TokenTypes#STATIC_INIT STATIC_INIT}, 040 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}, 041 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 042 * {@link TokenTypes#CTOR_DEF CTOR_DEF}, 043 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}. 044 * </p> 045 * 046 * <p> 047 * Example of declarations without empty line separator: 048 * </p> 049 * 050 * <pre> 051 * /////////////////////////////////////////////////// 052 * //HEADER 053 * /////////////////////////////////////////////////// 054 * package com.puppycrawl.tools.checkstyle.whitespace; 055 * import java.io.Serializable; 056 * class Foo 057 * { 058 * public static final int FOO_CONST = 1; 059 * public void foo() {} //should be separated from previous statement. 060 * } 061 * </pre> 062 * 063 * <p> An example of how to configure the check with default parameters is: 064 * </p> 065 * 066 * <pre> 067 * <module name="EmptyLineSeparator"/> 068 * </pre> 069 * 070 * <p> 071 * Example of declarations with empty line separator 072 * that is expected by the Check by default: 073 * </p> 074 * 075 * <pre> 076 * /////////////////////////////////////////////////// 077 * //HEADER 078 * /////////////////////////////////////////////////// 079 * 080 * package com.puppycrawl.tools.checkstyle.whitespace; 081 * 082 * import java.io.Serializable; 083 * 084 * class Foo 085 * { 086 * public static final int FOO_CONST = 1; 087 * 088 * public void foo() {} 089 * } 090 * </pre> 091 * <p> An example how to check empty line after 092 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and 093 * {@link TokenTypes#METHOD_DEF METHOD_DEF}: 094 * </p> 095 * 096 * <pre> 097 * <module name="EmptyLineSeparator"> 098 * <property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/> 099 * </module> 100 * </pre> 101 * 102 * <p> 103 * An example how to allow no empty line between fields: 104 * </p> 105 * <pre> 106 * <module name="EmptyLineSeparator"> 107 * <property name="allowNoEmptyLineBetweenFields" value="true"/> 108 * </module> 109 * </pre> 110 * 111 * <p> 112 * Example of declarations with multiple empty lines between class members (allowed by default): 113 * </p> 114 * 115 * <pre> 116 * /////////////////////////////////////////////////// 117 * //HEADER 118 * /////////////////////////////////////////////////// 119 * 120 * 121 * package com.puppycrawl.tools.checkstyle.whitespace; 122 * 123 * 124 * 125 * import java.io.Serializable; 126 * 127 * 128 * class Foo 129 * { 130 * public static final int FOO_CONST = 1; 131 * 132 * 133 * 134 * public void foo() {} 135 * } 136 * </pre> 137 * <p> 138 * An example how to disallow multiple empty lines between class members: 139 * </p> 140 * <pre> 141 * <module name="EmptyLineSeparator"> 142 * <property name="allowMultipleEmptyLines" value="false"/> 143 * </module> 144 * </pre> 145 * 146 * @author maxvetrenko 147 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 148 */ 149public class EmptyLineSeparatorCheck extends Check { 150 151 /** 152 * A key is pointing to the warning message empty.line.separator in "messages.properties" 153 * file. 154 */ 155 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator"; 156 157 /** 158 * A key is pointing to the warning message empty.line.separator.multiple.lines 159 * in "messages.properties" 160 * file. 161 */ 162 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines"; 163 164 /** 165 * A key is pointing to the warning message empty.line.separator.lines.after 166 * in "messages.properties" file. 167 */ 168 public static final String MSG_MULTIPLE_LINES_AFTER = 169 "empty.line.separator.multiple.lines.after"; 170 171 /** Allows no empty line between fields. */ 172 private boolean allowNoEmptyLineBetweenFields; 173 174 /** Allows multiple empty lines between class members. */ 175 private boolean allowMultipleEmptyLines = true; 176 177 /** 178 * Allow no empty line between fields. 179 * @param allow 180 * User's value. 181 */ 182 public final void setAllowNoEmptyLineBetweenFields(boolean allow) { 183 allowNoEmptyLineBetweenFields = allow; 184 } 185 186 /** 187 * Allow multiple empty lines between class members. 188 * @param allow User's value. 189 */ 190 public void setAllowMultipleEmptyLines(boolean allow) { 191 allowMultipleEmptyLines = allow; 192 } 193 194 @Override 195 public int[] getDefaultTokens() { 196 return getAcceptableTokens(); 197 } 198 199 @Override 200 public int[] getAcceptableTokens() { 201 return new int[] { 202 TokenTypes.PACKAGE_DEF, 203 TokenTypes.IMPORT, 204 TokenTypes.CLASS_DEF, 205 TokenTypes.INTERFACE_DEF, 206 TokenTypes.ENUM_DEF, 207 TokenTypes.STATIC_INIT, 208 TokenTypes.INSTANCE_INIT, 209 TokenTypes.METHOD_DEF, 210 TokenTypes.CTOR_DEF, 211 TokenTypes.VARIABLE_DEF, 212 }; 213 } 214 215 @Override 216 public int[] getRequiredTokens() { 217 return ArrayUtils.EMPTY_INT_ARRAY; 218 } 219 220 @Override 221 public void visitToken(DetailAST ast) { 222 if (hasMultipleLinesBefore(ast)) { 223 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 224 } 225 226 final DetailAST nextToken = ast.getNextSibling(); 227 if (nextToken != null) { 228 final int astType = ast.getType(); 229 switch (astType) { 230 case TokenTypes.VARIABLE_DEF: 231 processVariableDef(ast, nextToken); 232 break; 233 case TokenTypes.IMPORT: 234 processImport(ast, nextToken, astType); 235 break; 236 case TokenTypes.PACKAGE_DEF: 237 processPackage(ast, nextToken); 238 break; 239 default: 240 if (nextToken.getType() == TokenTypes.RCURLY) { 241 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) { 242 log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText()); 243 } 244 } 245 else if (!hasEmptyLineAfter(ast)) { 246 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 247 nextToken.getText()); 248 } 249 } 250 } 251 } 252 253 /** 254 * Whether the token has not allowed multiple empty lines before. 255 * @param ast the ast to check. 256 * @return true if the token has not allowed multiple empty lines before. 257 */ 258 private boolean hasMultipleLinesBefore(DetailAST ast) { 259 boolean result = false; 260 if ((ast.getType() != TokenTypes.VARIABLE_DEF 261 || isTypeField(ast)) 262 && hasNotAllowedTwoEmptyLinesBefore(ast)) { 263 result = true; 264 } 265 return result; 266 } 267 268 /** 269 * Process Package. 270 * @param ast token 271 * @param nextToken next token 272 */ 273 private void processPackage(DetailAST ast, DetailAST nextToken) { 274 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 275 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 276 } 277 if (!hasEmptyLineAfter(ast)) { 278 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 279 } 280 } 281 282 /** 283 * Process Import. 284 * @param ast token 285 * @param nextToken next token 286 * @param astType token Type 287 */ 288 private void processImport(DetailAST ast, DetailAST nextToken, int astType) { 289 if (astType != nextToken.getType() && !hasEmptyLineAfter(ast)) { 290 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 291 } 292 } 293 294 /** 295 * Process Variable. 296 * @param ast token 297 * @param nextToken next Token 298 */ 299 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 300 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 301 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 302 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 303 nextToken.getText()); 304 } 305 } 306 307 /** 308 * Checks whether token placement violates policy of empty line between fields. 309 * @param detailAST token to be analyzed 310 * @return true if policy is violated and warning should be raised; false otherwise 311 */ 312 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 313 return allowNoEmptyLineBetweenFields 314 && detailAST.getType() != TokenTypes.VARIABLE_DEF 315 && detailAST.getType() != TokenTypes.RCURLY 316 || !allowNoEmptyLineBetweenFields 317 && detailAST.getType() != TokenTypes.RCURLY; 318 } 319 320 /** 321 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 322 * @param token DetailAST token 323 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 324 */ 325 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 326 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 327 && isPrePreviousLineEmpty(token); 328 } 329 330 /** 331 * Checks if a token has empty pre-previous line. 332 * @param token DetailAST token. 333 * @return true, if token has empty lines before. 334 */ 335 private boolean isPrePreviousLineEmpty(DetailAST token) { 336 boolean result = false; 337 final int lineNo = token.getLineNo(); 338 // 3 is the number of the pre-previous line because the numbering starts from zero. 339 final int number = 3; 340 if (lineNo >= number) { 341 final String prePreviousLine = getLines()[lineNo - number]; 342 result = prePreviousLine.trim().isEmpty(); 343 } 344 return result; 345 } 346 347 /** 348 * Checks if token have empty line after. 349 * @param token token. 350 * @return true if token have empty line after. 351 */ 352 private boolean hasEmptyLineAfter(DetailAST token) { 353 DetailAST lastToken = token.getLastChild().getLastChild(); 354 if (lastToken == null) { 355 lastToken = token.getLastChild(); 356 } 357 // Start of the next token 358 final int nextBegin = token.getNextSibling().getLineNo(); 359 // End of current token. 360 final int currentEnd = lastToken.getLineNo(); 361 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 362 } 363 364 /** 365 * Checks, whether there are empty lines within the specified line range. Line numbering is 366 * started from 1 for parameter values 367 * @param startLine number of the first line in the range 368 * @param endLine number of the second line in the range 369 * @return <code>true</code> if found any blank line within the range, <code>false</code> 370 * otherwise 371 */ 372 private boolean hasEmptyLine(int startLine, int endLine) { 373 // Initial value is false - blank line not found 374 boolean result = false; 375 if (startLine <= endLine) { 376 final FileContents fileContents = getFileContents(); 377 for (int line = startLine; line <= endLine; line++) { 378 // Check, if the line is blank. Lines are numbered from 0, so subtract 1 379 if (fileContents.lineIsBlank(line - 1)) { 380 result = true; 381 break; 382 } 383 } 384 } 385 return result; 386 } 387 388 /** 389 * Checks if a token has a empty line before. 390 * @param token token. 391 * @return true, if token have empty line before. 392 */ 393 private boolean hasEmptyLineBefore(DetailAST token) { 394 final int lineNo = token.getLineNo(); 395 if (lineNo == 1) { 396 return false; 397 } 398 // [lineNo - 2] is the number of the previous line because the numbering starts from zero. 399 final String lineBefore = getLines()[lineNo - 2]; 400 return lineBefore.trim().isEmpty(); 401 } 402 403 /** 404 * If variable definition is a type field. 405 * @param variableDef variable definition. 406 * @return true variable definition is a type field. 407 */ 408 private static boolean isTypeField(DetailAST variableDef) { 409 final int parentType = variableDef.getParent().getParent().getType(); 410 return parentType == TokenTypes.CLASS_DEF; 411 } 412}