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