001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.blocks; 021 022import java.util.Locale; 023 024import org.apache.commons.beanutils.ConversionException; 025 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030 031/** 032 * <p> 033 * Checks the placement of left curly braces on types, methods and 034 * other blocks: 035 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, {@link 036 * TokenTypes#LITERAL_DO LITERAL_DO}, {@link TokenTypes#LITERAL_ELSE 037 * LITERAL_ELSE}, {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, {@link 038 * TokenTypes#LITERAL_FOR LITERAL_FOR}, {@link TokenTypes#LITERAL_IF 039 * LITERAL_IF}, {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, {@link 040 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, {@link 041 * TokenTypes#LITERAL_TRY LITERAL_TRY}, {@link TokenTypes#LITERAL_WHILE 042 * LITERAL_WHILE}, {@link TokenTypes#STATIC_INIT STATIC_INIT}, 043 * {@link TokenTypes#LAMBDA LAMBDA}. 044 * </p> 045 * 046 * <p> 047 * The policy to verify is specified using the {@link LeftCurlyOption} class and 048 * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL} 049 * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength. 050 * The default value for maxLineLength is 80. 051 * </p> 052 * <p> 053 * An example of how to configure the check is: 054 * </p> 055 * <pre> 056 * <module name="LeftCurly"/> 057 * </pre> 058 * <p> 059 * An example of how to configure the check with policy 060 * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is: 061 * </p> 062 * <pre> 063 * <module name="LeftCurly"> 064 * <property name="option" 065 * value="nlow"/> <property name="maxLineLength" value="120"/> < 066 * /module> 067 * </pre> 068 * <p> 069 * An example of how to configure the check to validate enum definitions: 070 * </p> 071 * <pre> 072 * <module name="LeftCurly"> 073 * <property name="ignoreEnums" value="false"/> 074 * </module> 075 * </pre> 076 * 077 * @author Oliver Burn 078 * @author lkuehne 079 * @author maxvetrenko 080 */ 081public class LeftCurlyCheck 082 extends AbstractCheck { 083 /** 084 * A key is pointing to the warning message text in "messages.properties" 085 * file. 086 */ 087 public static final String MSG_KEY_LINE_NEW = "line.new"; 088 089 /** 090 * A key is pointing to the warning message text in "messages.properties" 091 * file. 092 */ 093 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous"; 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 100 101 /** Open curly brace literal. */ 102 private static final String OPEN_CURLY_BRACE = "{"; 103 104 /** If true, Check will ignore enums. */ 105 private boolean ignoreEnums = true; 106 107 /** The policy to enforce. */ 108 private LeftCurlyOption option = LeftCurlyOption.EOL; 109 110 /** 111 * Set the option to enforce. 112 * @param optionStr string to decode option from 113 * @throws ConversionException if unable to decode 114 */ 115 public void setOption(String optionStr) { 116 try { 117 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 118 } 119 catch (IllegalArgumentException iae) { 120 throw new ConversionException("unable to parse " + optionStr, iae); 121 } 122 } 123 124 /** 125 * Sets the maximum line length used in calculating the placement of the 126 * left curly brace. 127 * @param maxLineLength the max allowed line length 128 * @deprecated since 6.10 release, option is not required for the Check. 129 */ 130 @Deprecated 131 public void setMaxLineLength(int maxLineLength) { 132 // do nothing, option is deprecated 133 } 134 135 /** 136 * Sets whether check should ignore enums when left curly brace policy is EOL. 137 * @param ignoreEnums check's option for ignoring enums. 138 */ 139 public void setIgnoreEnums(boolean ignoreEnums) { 140 this.ignoreEnums = ignoreEnums; 141 } 142 143 @Override 144 public int[] getDefaultTokens() { 145 return getAcceptableTokens(); 146 } 147 148 @Override 149 public int[] getAcceptableTokens() { 150 return new int[] { 151 TokenTypes.INTERFACE_DEF, 152 TokenTypes.CLASS_DEF, 153 TokenTypes.ANNOTATION_DEF, 154 TokenTypes.ENUM_DEF, 155 TokenTypes.CTOR_DEF, 156 TokenTypes.METHOD_DEF, 157 TokenTypes.ENUM_CONSTANT_DEF, 158 TokenTypes.LITERAL_WHILE, 159 TokenTypes.LITERAL_TRY, 160 TokenTypes.LITERAL_CATCH, 161 TokenTypes.LITERAL_FINALLY, 162 TokenTypes.LITERAL_SYNCHRONIZED, 163 TokenTypes.LITERAL_SWITCH, 164 TokenTypes.LITERAL_DO, 165 TokenTypes.LITERAL_IF, 166 TokenTypes.LITERAL_ELSE, 167 TokenTypes.LITERAL_FOR, 168 TokenTypes.STATIC_INIT, 169 TokenTypes.OBJBLOCK, 170 TokenTypes.LAMBDA, 171 }; 172 } 173 174 @Override 175 public int[] getRequiredTokens() { 176 return CommonUtils.EMPTY_INT_ARRAY; 177 } 178 179 @Override 180 public void visitToken(DetailAST ast) { 181 final DetailAST startToken; 182 DetailAST brace; 183 184 switch (ast.getType()) { 185 case TokenTypes.CTOR_DEF: 186 case TokenTypes.METHOD_DEF: 187 startToken = skipAnnotationOnlyLines(ast); 188 brace = ast.findFirstToken(TokenTypes.SLIST); 189 break; 190 case TokenTypes.INTERFACE_DEF: 191 case TokenTypes.CLASS_DEF: 192 case TokenTypes.ANNOTATION_DEF: 193 case TokenTypes.ENUM_DEF: 194 case TokenTypes.ENUM_CONSTANT_DEF: 195 startToken = skipAnnotationOnlyLines(ast); 196 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 197 brace = objBlock; 198 199 if (objBlock != null) { 200 brace = objBlock.getFirstChild(); 201 } 202 break; 203 case TokenTypes.LITERAL_WHILE: 204 case TokenTypes.LITERAL_CATCH: 205 case TokenTypes.LITERAL_SYNCHRONIZED: 206 case TokenTypes.LITERAL_FOR: 207 case TokenTypes.LITERAL_TRY: 208 case TokenTypes.LITERAL_FINALLY: 209 case TokenTypes.LITERAL_DO: 210 case TokenTypes.LITERAL_IF: 211 case TokenTypes.STATIC_INIT: 212 case TokenTypes.LAMBDA: 213 startToken = ast; 214 brace = ast.findFirstToken(TokenTypes.SLIST); 215 break; 216 case TokenTypes.LITERAL_ELSE: 217 startToken = ast; 218 final DetailAST candidate = ast.getFirstChild(); 219 brace = null; 220 221 if (candidate.getType() == TokenTypes.SLIST) { 222 brace = candidate; 223 } 224 break; 225 default: 226 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF, 227 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only. 228 // It has been done to improve coverage to 100%. I couldn't replace it with 229 // if-else-if block because code was ugly and didn't pass pmd check. 230 231 startToken = ast; 232 brace = ast.findFirstToken(TokenTypes.LCURLY); 233 break; 234 } 235 236 if (brace != null) { 237 verifyBrace(brace, startToken); 238 } 239 } 240 241 /** 242 * Skip lines that only contain {@code TokenTypes.ANNOTATION}s. 243 * If the received {@code DetailAST} 244 * has annotations within its modifiers then first token on the line 245 * of the first token after all annotations is return. This might be 246 * an annotation. 247 * Otherwise, the received {@code DetailAST} is returned. 248 * @param ast {@code DetailAST}. 249 * @return {@code DetailAST}. 250 */ 251 private static DetailAST skipAnnotationOnlyLines(DetailAST ast) { 252 DetailAST resultNode = ast; 253 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 254 255 if (modifiers != null) { 256 final DetailAST lastAnnotation = findLastAnnotation(modifiers); 257 258 if (lastAnnotation != null) { 259 final DetailAST tokenAfterLast; 260 261 if (lastAnnotation.getNextSibling() == null) { 262 tokenAfterLast = modifiers.getNextSibling(); 263 } 264 else { 265 tokenAfterLast = lastAnnotation.getNextSibling(); 266 } 267 268 if (tokenAfterLast.getLineNo() > lastAnnotation.getLineNo()) { 269 resultNode = tokenAfterLast; 270 } 271 else { 272 resultNode = getFirstAnnotationOnSameLine(lastAnnotation); 273 } 274 } 275 } 276 return resultNode; 277 } 278 279 /** 280 * Returns first annotation on same line. 281 * @param annotation 282 * last annotation on the line 283 * @return first annotation on same line. 284 */ 285 private static DetailAST getFirstAnnotationOnSameLine(DetailAST annotation) { 286 DetailAST previousAnnotation = annotation; 287 final int lastAnnotationLineNumber = previousAnnotation.getLineNo(); 288 while (previousAnnotation.getPreviousSibling() != null 289 && previousAnnotation.getPreviousSibling().getLineNo() 290 == lastAnnotationLineNumber) { 291 292 previousAnnotation = previousAnnotation.getPreviousSibling(); 293 } 294 return previousAnnotation; 295 } 296 297 /** 298 * Find the last token of type {@code TokenTypes.ANNOTATION} 299 * under the given set of modifiers. 300 * @param modifiers {@code DetailAST}. 301 * @return {@code DetailAST} or null if there are no annotations. 302 */ 303 private static DetailAST findLastAnnotation(DetailAST modifiers) { 304 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION); 305 while (annotation != null && annotation.getNextSibling() != null 306 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) { 307 annotation = annotation.getNextSibling(); 308 } 309 return annotation; 310 } 311 312 /** 313 * Verifies that a specified left curly brace is placed correctly 314 * according to policy. 315 * @param brace token for left curly brace 316 * @param startToken token for start of expression 317 */ 318 private void verifyBrace(final DetailAST brace, 319 final DetailAST startToken) { 320 final String braceLine = getLine(brace.getLineNo() - 1); 321 322 // Check for being told to ignore, or have '{}' which is a special case 323 if (braceLine.length() <= brace.getColumnNo() + 1 324 || braceLine.charAt(brace.getColumnNo() + 1) != '}') { 325 if (option == LeftCurlyOption.NL) { 326 if (!CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 327 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 328 } 329 } 330 else if (option == LeftCurlyOption.EOL) { 331 332 validateEol(brace, braceLine); 333 } 334 else if (startToken.getLineNo() != brace.getLineNo()) { 335 336 validateNewLinePosition(brace, startToken, braceLine); 337 338 } 339 } 340 } 341 342 /** 343 * Validate EOL case. 344 * @param brace brace AST 345 * @param braceLine line content 346 */ 347 private void validateEol(DetailAST brace, String braceLine) { 348 if (CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 349 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 350 } 351 if (!hasLineBreakAfter(brace)) { 352 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 353 } 354 } 355 356 /** 357 * Validate token on new Line position. 358 * @param brace brace AST 359 * @param startToken start Token 360 * @param braceLine content of line with Brace 361 */ 362 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) { 363 // not on the same line 364 if (startToken.getLineNo() + 1 == brace.getLineNo()) { 365 if (CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 366 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 367 } 368 else { 369 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 370 } 371 } 372 else if (!CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 373 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 374 } 375 } 376 377 /** 378 * Checks if left curly has line break after. 379 * @param leftCurly 380 * Left curly token. 381 * @return 382 * True, left curly has line break after. 383 */ 384 private boolean hasLineBreakAfter(DetailAST leftCurly) { 385 DetailAST nextToken = null; 386 if (leftCurly.getType() == TokenTypes.SLIST) { 387 nextToken = leftCurly.getFirstChild(); 388 } 389 else { 390 if (!ignoreEnums 391 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) { 392 nextToken = leftCurly.getNextSibling(); 393 } 394 } 395 return nextToken == null 396 || nextToken.getType() == TokenTypes.RCURLY 397 || leftCurly.getLineNo() != nextToken.getLineNo(); 398 } 399}