001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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 com.puppycrawl.tools.checkstyle.api.Check; 023import com.puppycrawl.tools.checkstyle.api.DetailAST; 024import com.puppycrawl.tools.checkstyle.api.TokenTypes; 025 026/** 027 * 028 * Checks for empty line separators after header, package, all import declarations, 029 * fields, constructors, methods, nested classes, 030 * static initializers and instance initializers. 031 * 032 * <p> By default the check will check the following statements: 033 * {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF}, 034 * {@link TokenTypes#IMPORT IMPORT}, 035 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 036 * {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF}, 037 * {@link TokenTypes#STATIC_INIT STATIC_INIT}, 038 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}, 039 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 040 * {@link TokenTypes#CTOR_DEF CTOR_DEF}, 041 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}. 042 * </p> 043 * 044 * <p> 045 * Example of declarations without empty line separator: 046 * </p> 047 * 048 * <pre> 049 * /////////////////////////////////////////////////// 050 * //HEADER 051 * /////////////////////////////////////////////////// 052 * package com.puppycrawl.tools.checkstyle.whitespace; 053 * import java.io.Serializable; 054 * class Foo 055 * { 056 * public static final int FOO_CONST = 1; 057 * public void foo() {} //should be separated from previous statement. 058 * } 059 * </pre> 060 * 061 * <p> An example of how to configure the check with default parameters is: 062 * </p> 063 * 064 * <pre> 065 * <module name="EmptyLineSeparator"/> 066 * </pre> 067 * 068 * <p> 069 * Example of declarations with empty line separator 070 * that is expected by the Check by default: 071 * </p> 072 * 073 * <pre> 074 * /////////////////////////////////////////////////// 075 * //HEADER 076 * /////////////////////////////////////////////////// 077 * 078 * package com.puppycrawl.tools.checkstyle.whitespace; 079 * 080 * import java.io.Serializable; 081 * 082 * class Foo 083 * { 084 * public static final int FOO_CONST = 1; 085 * 086 * public void foo() {} 087 * } 088 * </pre> 089 * <p> An example how to check empty line after 090 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and 091 * {@link TokenTypes#METHOD_DEF METHOD_DEF}: 092 * </p> 093 * 094 * <pre> 095 * <module name="EmptyLineSeparator"> 096 * <property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/> 097 * </module> 098 * </pre> 099 * 100 * <p> 101 * An example how to allow no empty line between fields: 102 * </p> 103 * <pre> 104 * <module name="EmptyLineSeparator"> 105 * <property name="allowNoEmptyLineBetweenFields" value="true"/> 106 * </module> 107 * </pre> 108 * 109 * <p> 110 * Example of declarations with multiple empty lines between class members (allowed by default): 111 * </p> 112 * 113 * <pre> 114 * /////////////////////////////////////////////////// 115 * //HEADER 116 * /////////////////////////////////////////////////// 117 * 118 * 119 * package com.puppycrawl.tools.checkstyle.whitespace; 120 * 121 * 122 * 123 * import java.io.Serializable; 124 * 125 * 126 * class Foo 127 * { 128 * public static final int FOO_CONST = 1; 129 * 130 * 131 * 132 * public void foo() {} 133 * } 134 * </pre> 135 * <p> 136 * An example how to disallow multiple empty lines between class members: 137 * </p> 138 * <pre> 139 * <module name="EmptyLineSeparator"> 140 * <property name="allowMultipleEmptyLines" value="false"/> 141 * </module> 142 * </pre> 143 * 144 * @author maxvetrenko 145 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 146 */ 147public class EmptyLineSeparatorCheck extends Check 148{ 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 /** */ 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 { 176 allowNoEmptyLineBetweenFields = allow; 177 } 178 179 /** 180 * Allow multiple empty lines between class members. 181 * @param allow User's value. 182 */ 183 public void setAllowMultipleEmptyLines(boolean allow) 184 { 185 allowMultipleEmptyLines = allow; 186 } 187 188 @Override 189 public int[] getDefaultTokens() 190 { 191 return new int[] { 192 TokenTypes.PACKAGE_DEF, 193 TokenTypes.IMPORT, 194 TokenTypes.CLASS_DEF, 195 TokenTypes.INTERFACE_DEF, 196 TokenTypes.ENUM_DEF, 197 TokenTypes.STATIC_INIT, 198 TokenTypes.INSTANCE_INIT, 199 TokenTypes.METHOD_DEF, 200 TokenTypes.CTOR_DEF, 201 TokenTypes.VARIABLE_DEF, 202 }; 203 } 204 205 @Override 206 public void visitToken(DetailAST ast) 207 { 208 final DetailAST nextToken = ast.getNextSibling(); 209 210 if (nextToken != null) { 211 final int astType = ast.getType(); 212 switch (astType) { 213 case TokenTypes.VARIABLE_DEF: 214 if (isTypeField(ast) && !hasEmptyLineAfter(ast)) { 215 if (allowNoEmptyLineBetweenFields 216 && nextToken.getType() != TokenTypes.VARIABLE_DEF 217 && nextToken.getType() != TokenTypes.RCURLY) 218 { 219 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 220 nextToken.getText()); 221 } 222 else if ((!allowNoEmptyLineBetweenFields || !allowMultipleEmptyLines) 223 && nextToken.getType() != TokenTypes.RCURLY) 224 { 225 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 226 nextToken.getText()); 227 } 228 } 229 if (!allowMultipleEmptyLines && isTypeField(ast) 230 && isPrePreviousLineEmpty(ast)) 231 { 232 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 233 } 234 break; 235 case TokenTypes.IMPORT: 236 if (astType != nextToken.getType() && !hasEmptyLineAfter(ast) 237 || (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast) 238 && ast.getPreviousSibling() == null)) 239 { 240 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 241 } 242 if (!allowMultipleEmptyLines && isPrePreviousLineEmpty(ast)) { 243 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 244 } 245 break; 246 case TokenTypes.PACKAGE_DEF: 247 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 248 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 249 } 250 if (!allowMultipleEmptyLines && isPrePreviousLineEmpty(ast)) { 251 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 252 } 253 default: 254 if (nextToken.getType() != TokenTypes.RCURLY && !hasEmptyLineAfter(ast)) { 255 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 256 } 257 if (!allowMultipleEmptyLines && isPrePreviousLineEmpty(ast)) { 258 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 259 } 260 } 261 } 262 } 263 264 /** 265 * Checks if a token has empty pre-previous line. 266 * @param token DetailAST token. 267 * @return true, if token has empty lines before. 268 */ 269 private boolean isPrePreviousLineEmpty(DetailAST token) 270 { 271 final int lineNo = token.getLineNo(); 272 // 3 is the number of the pre-previous line because the numbering starts from zero. 273 final int number = 3; 274 final String prePreviousLine = getLines()[lineNo - number]; 275 return prePreviousLine.trim().isEmpty(); 276 } 277 278 /** 279 * Checks if token have empty line after. 280 * @param token token. 281 * @return true if token have empty line after. 282 */ 283 private boolean hasEmptyLineAfter(DetailAST token) 284 { 285 DetailAST lastToken = token.getLastChild().getLastChild(); 286 if (null == lastToken) { 287 lastToken = token.getLastChild(); 288 } 289 return token.getNextSibling().getLineNo() - lastToken.getLineNo() > 1; 290 } 291 292 /** 293 * Checks if a token has a empty line before. 294 * @param token token. 295 * @return true, if token have empty line before. 296 */ 297 private boolean hasEmptyLineBefore(DetailAST token) 298 { 299 final int lineNo = token.getLineNo(); 300 // [lineNo - 2] is the number of the previous line because the numbering starts from zero. 301 final String lineBefore = getLines()[lineNo - 2]; 302 return lineBefore.trim().isEmpty(); 303 } 304 305 /** 306 * If variable definition is a type field. 307 * @param variableDef variable definition. 308 * @return true variable definition is a type field. 309 */ 310 private boolean isTypeField(DetailAST variableDef) 311 { 312 final int parentType = variableDef.getParent().getParent().getType(); 313 return parentType == TokenTypes.CLASS_DEF; 314 } 315}