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