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