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