001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 org.apache.commons.beanutils.ConversionException; 025 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.Scope; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 032import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 033 034/** 035 * <p> 036 * Checks the placement of right curly braces. 037 * The policy to verify is specified using the {@link RightCurlyOption} class 038 * and defaults to {@link RightCurlyOption#SAME}. 039 * </p> 040 * <p> By default the check will check the following tokens: 041 * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, 042 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, 043 * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, 044 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 045 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}. 046 * Other acceptable tokens are: 047 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 048 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 049 * {@link TokenTypes#CTOR_DEF CTOR_DEF}. 050 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}. 051 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}. 052 * {@link TokenTypes#LITERAL_DO LITERAL_DO}. 053 * {@link TokenTypes#STATIC_INIT STATIC_INIT}. 054 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}. 055 * </p> 056 * <p> 057 * <b>shouldStartLine</b> - does the check need to check 058 * if right curly starts line. Default value is <b>true</b> 059 * </p> 060 * <p> 061 * An example of how to configure the check is: 062 * </p> 063 * <pre> 064 * <module name="RightCurly"/> 065 * </pre> 066 * <p> 067 * An example of how to configure the check with policy 068 * {@link RightCurlyOption#ALONE} for {@code else} and 069 * {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is: 070 * </p> 071 * <pre> 072 * <module name="RightCurly"> 073 * <property name="tokens" value="LITERAL_ELSE"/> 074 * <property name="option" value="alone"/> 075 * </module> 076 * </pre> 077 * 078 * @author Oliver Burn 079 * @author lkuehne 080 * @author o_sukhodolsky 081 * @author maxvetrenko 082 * @author Andrei Selkin 083 * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a> 084 */ 085public class RightCurlyCheck extends AbstractCheck { 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 * A key is pointing to the warning message text in "messages.properties" 106 * file. 107 */ 108 public static final String MSG_KEY_LINE_NEW = "line.new"; 109 110 /** Do we need to check if right curly starts line. */ 111 private boolean shouldStartLine = true; 112 113 /** The policy to enforce. */ 114 private RightCurlyOption option = RightCurlyOption.SAME; 115 116 /** 117 * Set the option to enforce. 118 * @param optionStr string to decode option from 119 * @throws ConversionException if unable to decode 120 */ 121 public void setOption(String optionStr) { 122 try { 123 option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 124 } 125 catch (IllegalArgumentException iae) { 126 throw new ConversionException("unable to parse " + optionStr, iae); 127 } 128 } 129 130 /** 131 * Does the check need to check if right curly starts line. 132 * @param flag new value of this property. 133 */ 134 public void setShouldStartLine(boolean flag) { 135 shouldStartLine = flag; 136 } 137 138 @Override 139 public int[] getDefaultTokens() { 140 return new int[] { 141 TokenTypes.LITERAL_TRY, 142 TokenTypes.LITERAL_CATCH, 143 TokenTypes.LITERAL_FINALLY, 144 TokenTypes.LITERAL_IF, 145 TokenTypes.LITERAL_ELSE, 146 }; 147 } 148 149 @Override 150 public int[] getAcceptableTokens() { 151 return new int[] { 152 TokenTypes.LITERAL_TRY, 153 TokenTypes.LITERAL_CATCH, 154 TokenTypes.LITERAL_FINALLY, 155 TokenTypes.LITERAL_IF, 156 TokenTypes.LITERAL_ELSE, 157 TokenTypes.CLASS_DEF, 158 TokenTypes.METHOD_DEF, 159 TokenTypes.CTOR_DEF, 160 TokenTypes.LITERAL_FOR, 161 TokenTypes.LITERAL_WHILE, 162 TokenTypes.LITERAL_DO, 163 TokenTypes.STATIC_INIT, 164 TokenTypes.INSTANCE_INIT, 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 && rcurly.getType() == TokenTypes.RCURLY) { 179 final String violation; 180 if (shouldStartLine) { 181 final String targetSourceLine = getLines()[rcurly.getLineNo() - 1]; 182 violation = validate(details, option, true, targetSourceLine); 183 } 184 else { 185 violation = validate(details, option, false, ""); 186 } 187 188 if (!violation.isEmpty()) { 189 log(rcurly, violation, "}", rcurly.getColumnNo() + 1); 190 } 191 } 192 } 193 194 /** 195 * Does general validation. 196 * @param details for validation. 197 * @param bracePolicy for placing the right curly brace. 198 * @param shouldStartLine do we need to check if right curly starts line. 199 * @param targetSourceLine line that we need to check if shouldStartLine is true. 200 * @return violation message or empty string 201 * if there was not violation during validation. 202 */ 203 private static String validate(Details details, RightCurlyOption bracePolicy, 204 boolean shouldStartLine, String targetSourceLine) { 205 final DetailAST rcurly = details.rcurly; 206 final DetailAST lcurly = details.lcurly; 207 final DetailAST nextToken = details.nextToken; 208 final boolean shouldCheckLastRcurly = details.shouldCheckLastRcurly; 209 String violation = ""; 210 211 if (bracePolicy == RightCurlyOption.SAME 212 && !hasLineBreakBefore(rcurly) 213 && lcurly.getLineNo() != rcurly.getLineNo()) { 214 violation = MSG_KEY_LINE_BREAK_BEFORE; 215 } 216 else if (shouldCheckLastRcurly) { 217 if (rcurly.getLineNo() == nextToken.getLineNo()) { 218 violation = MSG_KEY_LINE_ALONE; 219 } 220 } 221 else if (shouldBeOnSameLine(bracePolicy, details)) { 222 violation = MSG_KEY_LINE_SAME; 223 } 224 else if (shouldBeAloneOnLine(bracePolicy, details)) { 225 violation = MSG_KEY_LINE_ALONE; 226 } 227 else if (shouldStartLine && !isOnStartOfLine(details, targetSourceLine)) { 228 violation = MSG_KEY_LINE_NEW; 229 } 230 return violation; 231 } 232 233 /** 234 * Checks that a right curly should be on the same line as the next statement. 235 * @param bracePolicy option for placing the right curly brace 236 * @param details Details for validation 237 * @return true if a right curly should be alone on a line. 238 */ 239 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) { 240 return bracePolicy == RightCurlyOption.SAME 241 && details.rcurly.getLineNo() != details.nextToken.getLineNo(); 242 } 243 244 /** 245 * Checks that a right curly should be alone on a line. 246 * @param bracePolicy option for placing the right curly brace 247 * @param details Details for validation 248 * @return true if a right curly should be alone on a line. 249 */ 250 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, Details details) { 251 return bracePolicy == RightCurlyOption.ALONE 252 && !isAloneOnLine(details) 253 && !isEmptyBody(details.lcurly) 254 || bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE 255 && !isAloneOnLine(details) 256 && !isSingleLineBlock(details) 257 && !isAnonInnerClassInit(details.lcurly) 258 && !isEmptyBody(details.lcurly); 259 } 260 261 /** 262 * Whether right curly brace starts target source line. 263 * @param details Details of right curly brace for validation 264 * @param targetSourceLine source line to check 265 * @return true if right curly brace starts target source line. 266 */ 267 private static boolean isOnStartOfLine(Details details, String targetSourceLine) { 268 return CommonUtils.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSourceLine) 269 || details.lcurly.getLineNo() == details.rcurly.getLineNo(); 270 } 271 272 /** 273 * Checks whether right curly is alone on a line. 274 * @param details for validation. 275 * @return true if right curly is alone on a line. 276 */ 277 private static boolean isAloneOnLine(Details details) { 278 final DetailAST rcurly = details.rcurly; 279 final DetailAST lcurly = details.lcurly; 280 final DetailAST nextToken = details.nextToken; 281 return rcurly.getLineNo() != lcurly.getLineNo() 282 && rcurly.getLineNo() != nextToken.getLineNo(); 283 } 284 285 /** 286 * Checks whether block has a single-line format. 287 * @param details for validation. 288 * @return true if block has single-line format. 289 */ 290 private static boolean isSingleLineBlock(Details details) { 291 final DetailAST rcurly = details.rcurly; 292 final DetailAST lcurly = details.lcurly; 293 final DetailAST nextToken = details.nextToken; 294 return rcurly.getLineNo() == lcurly.getLineNo() 295 && rcurly.getLineNo() != nextToken.getLineNo(); 296 } 297 298 /** 299 * Checks whether lcurly is in anonymous inner class initialization. 300 * @param lcurly left curly token. 301 * @return true if lcurly begins anonymous inner class initialization. 302 */ 303 private static boolean isAnonInnerClassInit(DetailAST lcurly) { 304 final Scope surroundingScope = ScopeUtils.getSurroundingScope(lcurly); 305 return surroundingScope.ordinal() == Scope.ANONINNER.ordinal(); 306 } 307 308 /** 309 * Collects validation details. 310 * @param ast detail ast. 311 * @return object that contain all details to make a validation. 312 */ 313 private static Details getDetails(DetailAST ast) { 314 // Attempt to locate the tokens to do the check 315 boolean shouldCheckLastRcurly = false; 316 DetailAST rcurly = null; 317 final DetailAST lcurly; 318 DetailAST nextToken; 319 320 switch (ast.getType()) { 321 case TokenTypes.LITERAL_TRY: 322 lcurly = ast.getFirstChild(); 323 nextToken = lcurly.getNextSibling(); 324 rcurly = lcurly.getLastChild(); 325 break; 326 case TokenTypes.LITERAL_CATCH: 327 nextToken = ast.getNextSibling(); 328 lcurly = ast.getLastChild(); 329 rcurly = lcurly.getLastChild(); 330 if (nextToken == null) { 331 shouldCheckLastRcurly = true; 332 nextToken = getNextToken(ast); 333 } 334 break; 335 case TokenTypes.LITERAL_IF: 336 nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 337 if (nextToken == null) { 338 shouldCheckLastRcurly = true; 339 nextToken = getNextToken(ast); 340 lcurly = ast.getLastChild(); 341 rcurly = lcurly.getLastChild(); 342 } 343 else { 344 lcurly = nextToken.getPreviousSibling(); 345 rcurly = lcurly.getLastChild(); 346 } 347 break; 348 case TokenTypes.LITERAL_ELSE: 349 case TokenTypes.LITERAL_FINALLY: 350 shouldCheckLastRcurly = true; 351 nextToken = getNextToken(ast); 352 lcurly = ast.getFirstChild(); 353 rcurly = lcurly.getLastChild(); 354 break; 355 case TokenTypes.CLASS_DEF: 356 final DetailAST child = ast.getLastChild(); 357 lcurly = child.getFirstChild(); 358 rcurly = child.getLastChild(); 359 nextToken = ast; 360 break; 361 case TokenTypes.CTOR_DEF: 362 case TokenTypes.STATIC_INIT: 363 case TokenTypes.INSTANCE_INIT: 364 lcurly = ast.findFirstToken(TokenTypes.SLIST); 365 rcurly = lcurly.getLastChild(); 366 nextToken = getNextToken(ast); 367 break; 368 case TokenTypes.LITERAL_DO: 369 nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 370 lcurly = ast.findFirstToken(TokenTypes.SLIST); 371 rcurly = lcurly.getLastChild(); 372 break; 373 default: 374 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF, 375 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, only. 376 // It has been done to improve coverage to 100%. I couldn't replace it with 377 // if-else-if block because code was ugly and didn't pass pmd check. 378 379 lcurly = ast.findFirstToken(TokenTypes.SLIST); 380 if (lcurly != null) { 381 // SLIST could be absent if method is abstract, 382 // and code like "while(true);" 383 rcurly = lcurly.getLastChild(); 384 } 385 nextToken = getNextToken(ast); 386 break; 387 } 388 389 final Details details = new Details(); 390 details.rcurly = rcurly; 391 details.lcurly = lcurly; 392 details.nextToken = nextToken; 393 details.shouldCheckLastRcurly = shouldCheckLastRcurly; 394 395 return details; 396 } 397 398 /** 399 * Checks if definition body is empty. 400 * @param lcurly left curly. 401 * @return true if definition body is empty. 402 */ 403 private static boolean isEmptyBody(DetailAST lcurly) { 404 boolean result = false; 405 if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) { 406 if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) { 407 result = true; 408 } 409 } 410 else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) { 411 result = true; 412 } 413 return result; 414 } 415 416 /** 417 * Finds next token after the given one. 418 * @param ast the given node. 419 * @return the token which represents next lexical item. 420 */ 421 private static DetailAST getNextToken(DetailAST ast) { 422 DetailAST next = null; 423 DetailAST parent = ast; 424 while (next == null) { 425 next = parent.getNextSibling(); 426 parent = parent.getParent(); 427 } 428 return CheckUtils.getFirstNode(next); 429 } 430 431 /** 432 * Checks if right curly has line break before. 433 * @param rightCurly right curly token. 434 * @return true, if right curly has line break before. 435 */ 436 private static boolean hasLineBreakBefore(DetailAST rightCurly) { 437 final DetailAST previousToken = rightCurly.getPreviousSibling(); 438 return previousToken == null 439 || rightCurly.getLineNo() != previousToken.getLineNo(); 440 } 441 442 /** 443 * Structure that contains all details for validation. 444 */ 445 private static class Details { 446 /** Right curly. */ 447 private DetailAST rcurly; 448 /** Left curly. */ 449 private DetailAST lcurly; 450 /** Next token. */ 451 private DetailAST nextToken; 452 /** Should check last right curly. */ 453 private boolean shouldCheckLastRcurly; 454 } 455}