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