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 = 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 * Collects validation details. 324 * @param ast detail ast. 325 * @return object that contain all details to make a validation. 326 * @noinspection SwitchStatementDensity 327 */ 328 // -@cs[JavaNCSS|ExecutableStatementCount|CyclomaticComplexity|NPathComplexity] getDetails() 329 // method is a huge SWITCH, it has to be monolithic 330 private static Details getDetails(DetailAST ast) { 331 // Attempt to locate the tokens to do the check 332 boolean shouldCheckLastRcurly = false; 333 DetailAST rcurly = null; 334 final DetailAST lcurly; 335 DetailAST nextToken; 336 337 switch (ast.getType()) { 338 case TokenTypes.LITERAL_TRY: 339 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 340 lcurly = ast.getFirstChild().getNextSibling(); 341 } 342 else { 343 lcurly = ast.getFirstChild(); 344 } 345 nextToken = lcurly.getNextSibling(); 346 rcurly = lcurly.getLastChild(); 347 348 if (nextToken == null) { 349 shouldCheckLastRcurly = true; 350 nextToken = getNextToken(ast); 351 } 352 break; 353 case TokenTypes.LITERAL_CATCH: 354 nextToken = ast.getNextSibling(); 355 lcurly = ast.getLastChild(); 356 rcurly = lcurly.getLastChild(); 357 if (nextToken == null) { 358 shouldCheckLastRcurly = true; 359 nextToken = getNextToken(ast); 360 } 361 break; 362 case TokenTypes.LITERAL_IF: 363 nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 364 if (nextToken == null) { 365 shouldCheckLastRcurly = true; 366 nextToken = getNextToken(ast); 367 lcurly = ast.getLastChild(); 368 } 369 else { 370 lcurly = nextToken.getPreviousSibling(); 371 } 372 if (lcurly.getType() == TokenTypes.SLIST) { 373 rcurly = lcurly.getLastChild(); 374 } 375 break; 376 case TokenTypes.LITERAL_ELSE: 377 case TokenTypes.LITERAL_FINALLY: 378 shouldCheckLastRcurly = true; 379 nextToken = getNextToken(ast); 380 lcurly = ast.getFirstChild(); 381 if (lcurly.getType() == TokenTypes.SLIST) { 382 rcurly = lcurly.getLastChild(); 383 } 384 break; 385 case TokenTypes.CLASS_DEF: 386 final DetailAST child = ast.getLastChild(); 387 lcurly = child.getFirstChild(); 388 rcurly = child.getLastChild(); 389 nextToken = ast; 390 break; 391 case TokenTypes.CTOR_DEF: 392 case TokenTypes.STATIC_INIT: 393 case TokenTypes.INSTANCE_INIT: 394 lcurly = ast.findFirstToken(TokenTypes.SLIST); 395 rcurly = lcurly.getLastChild(); 396 nextToken = getNextToken(ast); 397 break; 398 case TokenTypes.LITERAL_DO: 399 nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 400 lcurly = ast.findFirstToken(TokenTypes.SLIST); 401 if (lcurly != null) { 402 rcurly = lcurly.getLastChild(); 403 } 404 break; 405 case TokenTypes.LAMBDA: 406 lcurly = ast.findFirstToken(TokenTypes.SLIST); 407 nextToken = getNextToken(ast); 408 if (nextToken.getType() != TokenTypes.RPAREN 409 && nextToken.getType() != TokenTypes.COMMA) { 410 shouldCheckLastRcurly = true; 411 nextToken = getNextToken(nextToken); 412 } 413 if (lcurly != null) { 414 rcurly = lcurly.getLastChild(); 415 } 416 break; 417 default: 418 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF, 419 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE only. 420 // It has been done to improve coverage to 100%. I couldn't replace it with 421 // if-else-if block because code was ugly and didn't pass pmd check. 422 423 lcurly = ast.findFirstToken(TokenTypes.SLIST); 424 if (lcurly != null) { 425 // SLIST could be absent if method is abstract, 426 // and code like "while(true);" 427 rcurly = lcurly.getLastChild(); 428 } 429 nextToken = getNextToken(ast); 430 break; 431 } 432 433 final Details details = new Details(); 434 details.rcurly = rcurly; 435 details.lcurly = lcurly; 436 details.nextToken = nextToken; 437 details.shouldCheckLastRcurly = shouldCheckLastRcurly; 438 439 return details; 440 } 441 442 /** 443 * Checks if definition body is empty. 444 * @param lcurly left curly. 445 * @return true if definition body is empty. 446 */ 447 private static boolean isEmptyBody(DetailAST lcurly) { 448 boolean result = false; 449 if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) { 450 if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) { 451 result = true; 452 } 453 } 454 else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) { 455 result = true; 456 } 457 return result; 458 } 459 460 /** 461 * Finds next token after the given one. 462 * @param ast the given node. 463 * @return the token which represents next lexical item. 464 */ 465 private static DetailAST getNextToken(DetailAST ast) { 466 DetailAST next = null; 467 DetailAST parent = ast; 468 while (next == null) { 469 next = parent.getNextSibling(); 470 parent = parent.getParent(); 471 } 472 return CheckUtils.getFirstNode(next); 473 } 474 475 /** 476 * Checks if right curly has line break before. 477 * @param rightCurly right curly token. 478 * @return true, if right curly has line break before. 479 */ 480 private static boolean hasLineBreakBefore(DetailAST rightCurly) { 481 final DetailAST previousToken = rightCurly.getPreviousSibling(); 482 return previousToken == null 483 || rightCurly.getLineNo() != previousToken.getLineNo(); 484 } 485 486 /** 487 * Structure that contains all details for validation. 488 */ 489 private static class Details { 490 /** Right curly. */ 491 private DetailAST rcurly; 492 /** Left curly. */ 493 private DetailAST lcurly; 494 /** Next token. */ 495 private DetailAST nextToken; 496 /** Should check last right curly. */ 497 private boolean shouldCheckLastRcurly; 498 } 499}