001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.Scope; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 031import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 032 033/** 034 * <p> 035 * Checks the placement of right curly braces. 036 * The policy to verify is specified using the {@link RightCurlyOption} class 037 * and defaults to {@link RightCurlyOption#SAME}. 038 * </p> 039 * <p> By default the check will check the following tokens: 040 * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, 041 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, 042 * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, 043 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 044 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}. 045 * Other acceptable tokens are: 046 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 047 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 048 * {@link TokenTypes#CTOR_DEF CTOR_DEF}. 049 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}. 050 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}. 051 * {@link TokenTypes#LITERAL_DO LITERAL_DO}. 052 * {@link TokenTypes#STATIC_INIT STATIC_INIT}. 053 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}. 054 * {@link TokenTypes#LAMBDA LAMBDA}. 055 * </p> 056 * <p> 057 * <b>shouldStartLine</b> - does the check need to check 058 * if right curly starts line. Default value is <b>true</b> 059 * </p> 060 * <p> 061 * An example of how to configure the check is: 062 * </p> 063 * <pre> 064 * <module name="RightCurly"/> 065 * </pre> 066 * <p> 067 * An example of how to configure the check with policy 068 * {@link RightCurlyOption#ALONE} for {@code else} and 069 * {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is: 070 * </p> 071 * <pre> 072 * <module name="RightCurly"> 073 * <property name="tokens" value="LITERAL_ELSE"/> 074 * <property name="option" value="alone"/> 075 * </module> 076 * </pre> 077 * 078 */ 079@StatelessCheck 080public class RightCurlyCheck extends AbstractCheck { 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before"; 087 088 /** 089 * A key is pointing to the warning message text in "messages.properties" 090 * file. 091 */ 092 public static final String MSG_KEY_LINE_ALONE = "line.alone"; 093 094 /** 095 * A key is pointing to the warning message text in "messages.properties" 096 * file. 097 */ 098 public static final String MSG_KEY_LINE_SAME = "line.same"; 099 100 /** 101 * A key is pointing to the warning message text in "messages.properties" 102 * file. 103 */ 104 public static final String MSG_KEY_LINE_NEW = "line.new"; 105 106 /** Do we need to check if right curly starts line. */ 107 private boolean shouldStartLine = true; 108 109 /** The policy to enforce. */ 110 private RightCurlyOption option = RightCurlyOption.SAME; 111 112 /** 113 * Sets the option to enforce. 114 * @param optionStr string to decode option from 115 * @throws IllegalArgumentException if unable to decode 116 */ 117 public void setOption(String optionStr) { 118 try { 119 option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 120 } 121 catch (IllegalArgumentException iae) { 122 throw new IllegalArgumentException("unable to parse " + optionStr, iae); 123 } 124 } 125 126 /** 127 * Does the check need to check if right curly starts line. 128 * @param flag new value of this property. 129 */ 130 public void setShouldStartLine(boolean flag) { 131 shouldStartLine = flag; 132 } 133 134 @Override 135 public int[] getDefaultTokens() { 136 return new int[] { 137 TokenTypes.LITERAL_TRY, 138 TokenTypes.LITERAL_CATCH, 139 TokenTypes.LITERAL_FINALLY, 140 TokenTypes.LITERAL_IF, 141 TokenTypes.LITERAL_ELSE, 142 }; 143 } 144 145 @Override 146 public int[] getAcceptableTokens() { 147 return new int[] { 148 TokenTypes.LITERAL_TRY, 149 TokenTypes.LITERAL_CATCH, 150 TokenTypes.LITERAL_FINALLY, 151 TokenTypes.LITERAL_IF, 152 TokenTypes.LITERAL_ELSE, 153 TokenTypes.CLASS_DEF, 154 TokenTypes.METHOD_DEF, 155 TokenTypes.CTOR_DEF, 156 TokenTypes.LITERAL_FOR, 157 TokenTypes.LITERAL_WHILE, 158 TokenTypes.LITERAL_DO, 159 TokenTypes.STATIC_INIT, 160 TokenTypes.INSTANCE_INIT, 161 TokenTypes.LAMBDA, 162 }; 163 } 164 165 @Override 166 public int[] getRequiredTokens() { 167 return CommonUtil.EMPTY_INT_ARRAY; 168 } 169 170 @Override 171 public void visitToken(DetailAST ast) { 172 final Details details = Details.getDetails(ast); 173 final DetailAST rcurly = details.rcurly; 174 175 if (rcurly != null) { 176 final String violation = validate(details); 177 if (!violation.isEmpty()) { 178 log(rcurly, violation, "}", rcurly.getColumnNo() + 1); 179 } 180 } 181 } 182 183 /** 184 * Does general validation. 185 * @param details for validation. 186 * @return violation message or empty string 187 * if there was not violation during validation. 188 */ 189 private String validate(Details details) { 190 String violation = ""; 191 if (shouldHaveLineBreakBefore(option, details)) { 192 violation = MSG_KEY_LINE_BREAK_BEFORE; 193 } 194 else if (shouldBeOnSameLine(option, details)) { 195 violation = MSG_KEY_LINE_SAME; 196 } 197 else if (shouldBeAloneOnLine(option, details)) { 198 violation = MSG_KEY_LINE_ALONE; 199 } 200 else if (shouldStartLine) { 201 final String targetSourceLine = getLines()[details.rcurly.getLineNo() - 1]; 202 if (!isOnStartOfLine(details, targetSourceLine)) { 203 violation = MSG_KEY_LINE_NEW; 204 } 205 } 206 return violation; 207 } 208 209 /** 210 * Checks whether a right curly should have a line break before. 211 * @param bracePolicy option for placing the right curly brace. 212 * @param details details for validation. 213 * @return true if a right curly should have a line break before. 214 */ 215 private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy, 216 Details details) { 217 return bracePolicy == RightCurlyOption.SAME 218 && !hasLineBreakBefore(details.rcurly) 219 && details.lcurly.getLineNo() != details.rcurly.getLineNo(); 220 } 221 222 /** 223 * Checks that a right curly should be on the same line as the next statement. 224 * @param bracePolicy option for placing the right curly brace 225 * @param details Details for validation 226 * @return true if a right curly should be alone on a line. 227 */ 228 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) { 229 return bracePolicy == RightCurlyOption.SAME 230 && !details.shouldCheckLastRcurly 231 && details.rcurly.getLineNo() != details.nextToken.getLineNo(); 232 } 233 234 /** 235 * Checks that a right curly should be alone on a line. 236 * @param bracePolicy option for placing the right curly brace 237 * @param details Details for validation 238 * @return true if a right curly should be alone on a line. 239 */ 240 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, Details details) { 241 return bracePolicy == RightCurlyOption.ALONE 242 && shouldBeAloneOnLineWithAloneOption(details) 243 || bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE 244 && shouldBeAloneOnLineWithAloneOrSinglelineOption(details) 245 || details.shouldCheckLastRcurly 246 && details.rcurly.getLineNo() == details.nextToken.getLineNo(); 247 } 248 249 /** 250 * Whether right curly should be alone on line when ALONE option is used. 251 * @param details details for validation. 252 * @return true, if right curly should be alone on line when ALONE option is used. 253 */ 254 private static boolean shouldBeAloneOnLineWithAloneOption(Details details) { 255 return !isAloneOnLine(details) 256 && !isEmptyBody(details.lcurly); 257 } 258 259 /** 260 * Whether right curly should be alone on line when ALONE_OR_SINGLELINE option is used. 261 * @param details details for validation. 262 * @return true, if right curly should be alone on line 263 * when ALONE_OR_SINGLELINE option is used. 264 */ 265 private static boolean shouldBeAloneOnLineWithAloneOrSinglelineOption(Details details) { 266 return !isAloneOnLine(details) 267 && !isSingleLineBlock(details) 268 && !isAnonInnerClassInit(details.lcurly) 269 && !isEmptyBody(details.lcurly); 270 } 271 272 /** 273 * Whether right curly brace starts target source line. 274 * @param details Details of right curly brace for validation 275 * @param targetSourceLine source line to check 276 * @return true if right curly brace starts target source line. 277 */ 278 private static boolean isOnStartOfLine(Details details, String targetSourceLine) { 279 return CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSourceLine) 280 || details.lcurly.getLineNo() == details.rcurly.getLineNo(); 281 } 282 283 /** 284 * Checks whether right curly is alone on a line. 285 * @param details for validation. 286 * @return true if right curly is alone on a line. 287 */ 288 private static boolean isAloneOnLine(Details details) { 289 final DetailAST rcurly = details.rcurly; 290 final DetailAST lcurly = details.lcurly; 291 final DetailAST nextToken = details.nextToken; 292 return rcurly.getLineNo() != lcurly.getLineNo() 293 && rcurly.getLineNo() != nextToken.getLineNo(); 294 } 295 296 /** 297 * Checks whether block has a single-line format. 298 * @param details for validation. 299 * @return true if block has single-line format. 300 */ 301 private static boolean isSingleLineBlock(Details details) { 302 final DetailAST rcurly = details.rcurly; 303 final DetailAST lcurly = details.lcurly; 304 final DetailAST nextToken = details.nextToken; 305 return rcurly.getLineNo() == lcurly.getLineNo() 306 && rcurly.getLineNo() != nextToken.getLineNo(); 307 } 308 309 /** 310 * Checks whether lcurly is in anonymous inner class initialization. 311 * @param lcurly left curly token. 312 * @return true if lcurly begins anonymous inner class initialization. 313 */ 314 private static boolean isAnonInnerClassInit(DetailAST lcurly) { 315 final Scope surroundingScope = ScopeUtil.getSurroundingScope(lcurly); 316 return surroundingScope.ordinal() == Scope.ANONINNER.ordinal(); 317 } 318 319 /** 320 * Checks if definition body is empty. 321 * @param lcurly left curly. 322 * @return true if definition body is empty. 323 */ 324 private static boolean isEmptyBody(DetailAST lcurly) { 325 boolean result = false; 326 if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) { 327 if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) { 328 result = true; 329 } 330 } 331 else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) { 332 result = true; 333 } 334 return result; 335 } 336 337 /** 338 * Checks if right curly has line break before. 339 * @param rightCurly right curly token. 340 * @return true, if right curly has line break before. 341 */ 342 private static boolean hasLineBreakBefore(DetailAST rightCurly) { 343 final DetailAST previousToken = rightCurly.getPreviousSibling(); 344 return previousToken == null 345 || rightCurly.getLineNo() != previousToken.getLineNo(); 346 } 347 348 /** 349 * Structure that contains all details for validation. 350 */ 351 private static final class Details { 352 353 /** Right curly. */ 354 private final DetailAST rcurly; 355 /** Left curly. */ 356 private final DetailAST lcurly; 357 /** Next token. */ 358 private final DetailAST nextToken; 359 /** Should check last right curly. */ 360 private final boolean shouldCheckLastRcurly; 361 362 /** 363 * Constructor. 364 * @param lcurly the lcurly of the token whose details are being collected 365 * @param rcurly the rcurly of the token whose details are being collected 366 * @param nextToken the token after the token whose details are being collected 367 * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly 368 */ 369 private Details(DetailAST lcurly, DetailAST rcurly, 370 DetailAST nextToken, boolean shouldCheckLastRcurly) { 371 this.lcurly = lcurly; 372 this.rcurly = rcurly; 373 this.nextToken = nextToken; 374 this.shouldCheckLastRcurly = shouldCheckLastRcurly; 375 } 376 377 /** 378 * Collects validation Details. 379 * @param ast a {@code DetailAST} value 380 * @return object containing all details to make a validation 381 */ 382 private static Details getDetails(DetailAST ast) { 383 final Details details; 384 switch (ast.getType()) { 385 case TokenTypes.LITERAL_TRY: 386 case TokenTypes.LITERAL_CATCH: 387 case TokenTypes.LITERAL_FINALLY: 388 details = getDetailsForTryCatchFinally(ast); 389 break; 390 case TokenTypes.LITERAL_IF: 391 case TokenTypes.LITERAL_ELSE: 392 details = getDetailsForIfElse(ast); 393 break; 394 case TokenTypes.LITERAL_DO: 395 case TokenTypes.LITERAL_WHILE: 396 case TokenTypes.LITERAL_FOR: 397 details = getDetailsForLoops(ast); 398 break; 399 case TokenTypes.LAMBDA: 400 details = getDetailsForLambda(ast); 401 break; 402 default: 403 details = getDetailsForOthers(ast); 404 break; 405 } 406 return details; 407 } 408 409 /** 410 * Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY. 411 * @param ast a {@code DetailAST} value 412 * @return object containing all details to make a validation 413 */ 414 private static Details getDetailsForTryCatchFinally(DetailAST ast) { 415 boolean shouldCheckLastRcurly = false; 416 final DetailAST rcurly; 417 final DetailAST lcurly; 418 DetailAST nextToken; 419 final int tokenType = ast.getType(); 420 if (tokenType == TokenTypes.LITERAL_TRY) { 421 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 422 lcurly = ast.getFirstChild().getNextSibling(); 423 } 424 else { 425 lcurly = ast.getFirstChild(); 426 } 427 nextToken = lcurly.getNextSibling(); 428 rcurly = lcurly.getLastChild(); 429 430 if (nextToken == null) { 431 shouldCheckLastRcurly = true; 432 nextToken = getNextToken(ast); 433 } 434 } 435 else if (tokenType == TokenTypes.LITERAL_CATCH) { 436 nextToken = ast.getNextSibling(); 437 lcurly = ast.getLastChild(); 438 rcurly = lcurly.getLastChild(); 439 if (nextToken == null) { 440 shouldCheckLastRcurly = true; 441 nextToken = getNextToken(ast); 442 } 443 } 444 else { 445 shouldCheckLastRcurly = true; 446 nextToken = getNextToken(ast); 447 lcurly = ast.getFirstChild(); 448 rcurly = lcurly.getLastChild(); 449 } 450 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 451 } 452 453 /** 454 * Collects validation details for LITERAL_IF and LITERAL_ELSE. 455 * @param ast a {@code DetailAST} value 456 * @return object containing all details to make a validation 457 */ 458 private static Details getDetailsForIfElse(DetailAST ast) { 459 boolean shouldCheckLastRcurly = false; 460 final DetailAST lcurly; 461 DetailAST nextToken; 462 final int tokenType = ast.getType(); 463 if (tokenType == TokenTypes.LITERAL_IF) { 464 nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 465 if (nextToken == null) { 466 shouldCheckLastRcurly = true; 467 nextToken = getNextToken(ast); 468 lcurly = ast.getLastChild(); 469 } 470 else { 471 lcurly = nextToken.getPreviousSibling(); 472 } 473 } 474 else { 475 shouldCheckLastRcurly = true; 476 nextToken = getNextToken(ast); 477 lcurly = ast.getFirstChild(); 478 } 479 DetailAST rcurly = null; 480 if (lcurly.getType() == TokenTypes.SLIST) { 481 rcurly = lcurly.getLastChild(); 482 } 483 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 484 } 485 486 /** 487 * Collects validation details for CLASS_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, and 488 * INSTANCE_INIT. 489 * @param ast a {@code DetailAST} value 490 * @return an object containing all details to make a validation 491 */ 492 private static Details getDetailsForOthers(DetailAST ast) { 493 DetailAST rcurly = null; 494 final DetailAST lcurly; 495 final DetailAST nextToken; 496 final int tokenType = ast.getType(); 497 if (tokenType == TokenTypes.CLASS_DEF) { 498 final DetailAST child = ast.getLastChild(); 499 lcurly = child.getFirstChild(); 500 rcurly = child.getLastChild(); 501 nextToken = ast; 502 } 503 else if (tokenType == TokenTypes.METHOD_DEF) { 504 lcurly = ast.findFirstToken(TokenTypes.SLIST); 505 if (lcurly != null) { 506 // SLIST could be absent if method is abstract 507 rcurly = lcurly.getLastChild(); 508 } 509 nextToken = getNextToken(ast); 510 } 511 else { 512 lcurly = ast.findFirstToken(TokenTypes.SLIST); 513 rcurly = lcurly.getLastChild(); 514 nextToken = getNextToken(ast); 515 } 516 return new Details(lcurly, rcurly, nextToken, false); 517 } 518 519 /** 520 * Collects validation details for loops' tokens. 521 * @param ast a {@code DetailAST} value 522 * @return an object containing all details to make a validation 523 */ 524 private static Details getDetailsForLoops(DetailAST ast) { 525 DetailAST rcurly = null; 526 final DetailAST lcurly; 527 final DetailAST nextToken; 528 final int tokenType = ast.getType(); 529 if (tokenType == TokenTypes.LITERAL_DO) { 530 nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 531 lcurly = ast.findFirstToken(TokenTypes.SLIST); 532 if (lcurly != null) { 533 rcurly = lcurly.getLastChild(); 534 } 535 } 536 else { 537 lcurly = ast.findFirstToken(TokenTypes.SLIST); 538 if (lcurly != null) { 539 // SLIST could be absent in code like "while(true);" 540 rcurly = lcurly.getLastChild(); 541 } 542 nextToken = getNextToken(ast); 543 } 544 return new Details(lcurly, rcurly, nextToken, false); 545 } 546 547 /** 548 * Collects validation details for Lambdas. 549 * @param ast a {@code DetailAST} value 550 * @return an object containing all details to make a validation 551 */ 552 private static Details getDetailsForLambda(DetailAST ast) { 553 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST); 554 boolean shouldCheckLastRcurly = false; 555 DetailAST nextToken = getNextToken(ast); 556 if (nextToken.getType() != TokenTypes.RPAREN 557 && nextToken.getType() != TokenTypes.COMMA) { 558 shouldCheckLastRcurly = true; 559 nextToken = getNextToken(nextToken); 560 } 561 DetailAST rcurly = null; 562 if (lcurly != null) { 563 rcurly = lcurly.getLastChild(); 564 } 565 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 566 } 567 568 /** 569 * Finds next token after the given one. 570 * @param ast the given node. 571 * @return the token which represents next lexical item. 572 */ 573 private static DetailAST getNextToken(DetailAST ast) { 574 DetailAST next = null; 575 DetailAST parent = ast; 576 while (next == null) { 577 next = parent.getNextSibling(); 578 parent = parent.getParent(); 579 } 580 return CheckUtil.getFirstNode(next); 581 } 582 583 } 584 585}