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