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