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