001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.blocks; 020 021import com.puppycrawl.tools.checkstyle.api.DetailAST; 022import com.puppycrawl.tools.checkstyle.api.TokenTypes; 023import com.puppycrawl.tools.checkstyle.api.Utils; 024import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck; 025import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 026 027/** 028 * <p> 029 * Checks the placement of right curly braces. 030 * The policy to verify is specified using the {@link RightCurlyOption} class 031 * and defaults to {@link RightCurlyOption#SAME}. 032 * </p> 033 * <p> By default the check will check the following tokens: 034 * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, 035 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, 036 * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, 037 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 038 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}. 039 * Other acceptable tokens are: 040 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 041 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 042 * {@link TokenTypes#CTOR_DEF CTOR_DEF}. 043 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}. 044 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}. 045 * {@link TokenTypes#LITERAL_DO LITERAL_DO}. 046 * {@link TokenTypes#STATIC_INIT STATIC_INIT}. 047 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}. 048 * </p> 049 * <p> 050 * An example of how to configure the check is: 051 * </p> 052 * <pre> 053 * <module name="RightCurly"/> 054 * </pre> 055 * <p> 056 * An example of how to configure the check with policy 057 * {@link RightCurlyOption#ALONE} for <code>else</code> and 058 * <code>{@link TokenTypes#METHOD_DEF METHOD_DEF}</code>tokens is: 059 * </p> 060 * <pre> 061 * <module name="RightCurly"> 062 * <property name="tokens" value="LITERAL_ELSE"/> 063 * <property name="option" value="alone"/> 064 * </module> 065 * </pre> 066 * 067 * @author Oliver Burn 068 * @author lkuehne 069 * @author o_sukhodolsky 070 * @author maxvetrenko 071 * @version 2.0 072 */ 073public class RightCurlyCheck extends AbstractOptionCheck<RightCurlyOption> 074{ 075 /** Do we need to check if rculry starts line. */ 076 private boolean shouldStartLine = true; 077 078 /** 079 * A key is pointing to the warning message text in "messages.properties" 080 * file. 081 */ 082 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before"; 083 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_ALONE = "line.alone"; 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_SAME = "line.same"; 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_NEW = "line.new"; 101 102 /** 103 * Sets the right curly option to same. 104 */ 105 public RightCurlyCheck() 106 { 107 super(RightCurlyOption.SAME, RightCurlyOption.class); 108 } 109 110 /** 111 * Does the check need to check if rcurly starts line. 112 * @param flag new value of this property. 113 */ 114 public void setShouldStartLine(boolean flag) 115 { 116 shouldStartLine = flag; 117 } 118 119 @Override 120 public int[] getDefaultTokens() 121 { 122 return new int[] { 123 TokenTypes.LITERAL_TRY, 124 TokenTypes.LITERAL_CATCH, 125 TokenTypes.LITERAL_FINALLY, 126 TokenTypes.LITERAL_IF, 127 TokenTypes.LITERAL_ELSE, 128 }; 129 } 130 131 @Override 132 public int[] getAcceptableTokens() 133 { 134 return new int[] { 135 TokenTypes.LITERAL_TRY, 136 TokenTypes.LITERAL_CATCH, 137 TokenTypes.LITERAL_FINALLY, 138 TokenTypes.LITERAL_IF, 139 TokenTypes.LITERAL_ELSE, 140 TokenTypes.CLASS_DEF, 141 TokenTypes.METHOD_DEF, 142 TokenTypes.CTOR_DEF, 143 TokenTypes.LITERAL_FOR, 144 TokenTypes.LITERAL_WHILE, 145 TokenTypes.LITERAL_DO, 146 TokenTypes.STATIC_INIT, 147 TokenTypes.INSTANCE_INIT, 148 }; 149 } 150 151 @Override 152 public void visitToken(DetailAST ast) 153 { 154 // Attempt to locate the tokens to do the check 155 DetailAST rcurly; 156 DetailAST lcurly; 157 DetailAST nextToken; 158 boolean shouldCheckLastRcurly = false; 159 160 switch (ast.getType()) { 161 case TokenTypes.LITERAL_TRY: 162 lcurly = ast.getFirstChild(); 163 nextToken = lcurly.getNextSibling(); 164 rcurly = lcurly.getLastChild(); 165 break; 166 case TokenTypes.LITERAL_CATCH: 167 nextToken = ast.getNextSibling(); 168 lcurly = ast.getLastChild(); 169 rcurly = lcurly.getLastChild(); 170 if (nextToken == null) { 171 shouldCheckLastRcurly = true; 172 nextToken = getNextToken(ast); 173 } 174 break; 175 case TokenTypes.LITERAL_IF: 176 nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 177 if (nextToken != null) { 178 lcurly = nextToken.getPreviousSibling(); 179 rcurly = lcurly.getLastChild(); 180 } 181 else { 182 shouldCheckLastRcurly = true; 183 nextToken = getNextToken(ast); 184 lcurly = ast.getLastChild(); 185 rcurly = lcurly.getLastChild(); 186 } 187 break; 188 case TokenTypes.LITERAL_ELSE: 189 shouldCheckLastRcurly = true; 190 nextToken = getNextToken(ast); 191 lcurly = ast.getFirstChild(); 192 rcurly = lcurly.getLastChild(); 193 break; 194 case TokenTypes.LITERAL_FINALLY: 195 shouldCheckLastRcurly = true; 196 nextToken = getNextToken(ast); 197 lcurly = ast.getFirstChild(); 198 rcurly = lcurly.getLastChild(); 199 break; 200 case TokenTypes.CLASS_DEF: 201 final DetailAST child = ast.getLastChild(); 202 lcurly = child.getFirstChild(); 203 rcurly = child.getLastChild(); 204 nextToken = ast; 205 break; 206 case TokenTypes.CTOR_DEF: 207 case TokenTypes.STATIC_INIT: 208 case TokenTypes.INSTANCE_INIT: 209 lcurly = ast.findFirstToken(TokenTypes.SLIST); 210 rcurly = lcurly.getLastChild(); 211 nextToken = ast; 212 break; 213 case TokenTypes.METHOD_DEF: 214 case TokenTypes.LITERAL_FOR: 215 case TokenTypes.LITERAL_WHILE: 216 case TokenTypes.LITERAL_DO: 217 lcurly = ast.findFirstToken(TokenTypes.SLIST); 218 //SLIST could be absent if method is abstract, and code like "while(true);" 219 if (lcurly == null) { 220 return; 221 } 222 rcurly = lcurly.getLastChild(); 223 nextToken = ast; 224 break; 225 default: 226 throw new RuntimeException("Unexpected token type (" 227 + TokenTypes.getTokenName(ast.getType()) + ")"); 228 } 229 230 if ((rcurly == null) || (rcurly.getType() != TokenTypes.RCURLY)) { 231 // we need to have both tokens to perform the check 232 return; 233 } 234 235 if (getAbstractOption() == RightCurlyOption.SAME && !hasLineBreakBefore(rcurly)) { 236 log(rcurly, MSG_KEY_LINE_BREAK_BEFORE); 237 } 238 239 if (shouldCheckLastRcurly) { 240 if (rcurly.getLineNo() == nextToken.getLineNo()) { 241 log(rcurly, MSG_KEY_LINE_ALONE, "}"); 242 } 243 } 244 else if ((getAbstractOption() == RightCurlyOption.SAME) 245 && (rcurly.getLineNo() != nextToken.getLineNo())) 246 { 247 log(rcurly, MSG_KEY_LINE_SAME, "}"); 248 } 249 else if ((getAbstractOption() == RightCurlyOption.ALONE) 250 && (rcurly.getLineNo() == nextToken.getLineNo()) 251 && !isEmptyBody(lcurly)) 252 { 253 log(rcurly, MSG_KEY_LINE_ALONE, "}"); 254 } 255 256 if (!shouldStartLine) { 257 return; 258 } 259 final boolean startsLine = 260 Utils.whitespaceBefore(rcurly.getColumnNo(), 261 getLines()[rcurly.getLineNo() - 1]); 262 263 if (!startsLine && (lcurly.getLineNo() != rcurly.getLineNo())) { 264 log(rcurly, MSG_KEY_LINE_NEW, "}"); 265 } 266 } 267 268 /** 269 * Checks if definition body is empty. 270 * @param lcurly left curly. 271 * @return true if definition body is empty. 272 */ 273 private boolean isEmptyBody(DetailAST lcurly) 274 { 275 boolean result = false; 276 if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) { 277 if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) { 278 result = true; 279 } 280 } 281 else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) { 282 result = true; 283 } 284 return result; 285 } 286 287 /** 288 * Finds next token after the given one. 289 * @param ast the given node. 290 * @return the token which represents next lexical item. 291 */ 292 private DetailAST getNextToken(DetailAST ast) 293 { 294 DetailAST next = null; 295 DetailAST parent = ast; 296 while ((parent != null) && (next == null)) { 297 next = parent.getNextSibling(); 298 parent = parent.getParent(); 299 } 300 return CheckUtils.getFirstNode(next); 301 } 302 303 /** 304 * Checks if right curly has line break before. 305 * @param rightCurly 306 * Right curly token. 307 * @return 308 * True, if right curly has line break before. 309 */ 310 private boolean hasLineBreakBefore(DetailAST rightCurly) 311 { 312 if (rightCurly != null) { 313 final DetailAST previousToken = rightCurly.getPreviousSibling(); 314 if (previousToken != null && rightCurly.getLineNo() == previousToken.getLineNo()) { 315 return false; 316 } 317 } 318 return true; 319 } 320}