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; 025 026/** 027 * <p> 028 * Checks the placement of left curly braces on types, methods and 029 * other blocks: 030 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, {@link 031 * TokenTypes#LITERAL_DO LITERAL_DO}, {@link TokenTypes#LITERAL_ELSE 032 * LITERAL_ELSE}, {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, {@link 033 * TokenTypes#LITERAL_FOR LITERAL_FOR}, {@link TokenTypes#LITERAL_IF 034 * LITERAL_IF}, {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, {@link 035 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, {@link 036 * TokenTypes#LITERAL_TRY LITERAL_TRY}, {@link TokenTypes#LITERAL_WHILE 037 * LITERAL_WHILE}. 038 * </p> 039 * 040 * <p> 041 * The policy to verify is specified using the {@link LeftCurlyOption} class and 042 * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL} 043 * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength. 044 * The default value for maxLineLength is 80. 045 * </p> 046 * <p> 047 * An example of how to configure the check is: 048 * </p> 049 * <pre> 050 * <module name="LeftCurly"/> 051 * </pre> 052 * <p> 053 * An example of how to configure the check with policy 054 * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is: 055 * </p> 056 * <pre> 057 * <module name="LeftCurly"> 058 * <property name="option" 059 * value="nlow"/> <property name="maxLineLength" value="120"/> < 060 * /module> 061 * </pre> 062 * <p> 063 * An example of how to configure the check to validate enum definitions: 064 * </p> 065 * <pre> 066 * <module name="LeftCurly"> 067 * <property name="ignoreEnums" value="false"/> 068 * </module> 069 * </pre> 070 * 071 * @author Oliver Burn 072 * @author lkuehne 073 * @author maxvetrenko 074 * @version 1.0 075 */ 076public class LeftCurlyCheck 077 extends AbstractOptionCheck<LeftCurlyOption> 078{ 079 /** default maximum line length */ 080 private static final int DEFAULT_MAX_LINE_LENGTH = 80; 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_KEY_LINE_NEW = "line.new"; 087 088 /** 089 * A key is pointing to the warning message text in "messages.properties" 090 * file. 091 */ 092 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous"; 093 094 /** 095 * A key is pointing to the warning message text in "messages.properties" 096 * file. 097 */ 098 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 099 100 /** TODO: replace this ugly hack **/ 101 private int maxLineLength = DEFAULT_MAX_LINE_LENGTH; 102 103 /** If true, Check will ignore enums*/ 104 private boolean ignoreEnums = true; 105 106 /** 107 * Creates a default instance and sets the policy to EOL. 108 */ 109 public LeftCurlyCheck() 110 { 111 super(LeftCurlyOption.EOL, LeftCurlyOption.class); 112 } 113 114 /** 115 * Sets the maximum line length used in calculating the placement of the 116 * left curly brace. 117 * @param maxLineLength the max allowed line length 118 */ 119 public void setMaxLineLength(int maxLineLength) 120 { 121 this.maxLineLength = maxLineLength; 122 } 123 124 @Override 125 public int[] getDefaultTokens() 126 { 127 return new int[] { 128 TokenTypes.INTERFACE_DEF, 129 TokenTypes.CLASS_DEF, 130 TokenTypes.ANNOTATION_DEF, 131 TokenTypes.ENUM_DEF, 132 TokenTypes.CTOR_DEF, 133 TokenTypes.METHOD_DEF, 134 TokenTypes.ENUM_CONSTANT_DEF, 135 TokenTypes.LITERAL_WHILE, 136 TokenTypes.LITERAL_TRY, 137 TokenTypes.LITERAL_CATCH, 138 TokenTypes.LITERAL_FINALLY, 139 TokenTypes.LITERAL_SYNCHRONIZED, 140 TokenTypes.LITERAL_SWITCH, 141 TokenTypes.LITERAL_DO, 142 TokenTypes.LITERAL_IF, 143 TokenTypes.LITERAL_ELSE, 144 TokenTypes.LITERAL_FOR, 145 // TODO: need to handle.... 146 //TokenTypes.STATIC_INIT, 147 }; 148 } 149 150 @Override 151 public void visitToken(DetailAST ast) 152 { 153 final DetailAST startToken; 154 final DetailAST brace; 155 156 switch (ast.getType()) { 157 case TokenTypes.CTOR_DEF : 158 case TokenTypes.METHOD_DEF : 159 startToken = skipAnnotationOnlyLines(ast); 160 brace = ast.findFirstToken(TokenTypes.SLIST); 161 break; 162 163 case TokenTypes.INTERFACE_DEF : 164 case TokenTypes.CLASS_DEF : 165 case TokenTypes.ANNOTATION_DEF : 166 case TokenTypes.ENUM_DEF : 167 case TokenTypes.ENUM_CONSTANT_DEF : 168 startToken = skipAnnotationOnlyLines(ast); 169 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 170 brace = (objBlock == null) 171 ? null 172 : objBlock.getFirstChild(); 173 break; 174 175 case TokenTypes.LITERAL_WHILE: 176 case TokenTypes.LITERAL_CATCH: 177 case TokenTypes.LITERAL_SYNCHRONIZED: 178 case TokenTypes.LITERAL_FOR: 179 case TokenTypes.LITERAL_TRY: 180 case TokenTypes.LITERAL_FINALLY: 181 case TokenTypes.LITERAL_DO: 182 case TokenTypes.LITERAL_IF : 183 startToken = ast; 184 brace = ast.findFirstToken(TokenTypes.SLIST); 185 break; 186 187 case TokenTypes.LITERAL_ELSE : 188 startToken = ast; 189 final DetailAST candidate = ast.getFirstChild(); 190 brace = 191 (candidate.getType() == TokenTypes.SLIST) 192 ? candidate 193 : null; // silently ignore 194 break; 195 196 case TokenTypes.LITERAL_SWITCH : 197 startToken = ast; 198 brace = ast.findFirstToken(TokenTypes.LCURLY); 199 break; 200 201 default : 202 startToken = null; 203 brace = null; 204 } 205 206 if ((brace != null) && (startToken != null)) { 207 verifyBrace(brace, startToken); 208 } 209 } 210 211 /** 212 * Skip lines that only contain <code>TokenTypes.ANNOTATION</code>s. 213 * If the received <code>DetailAST</code> 214 * has annotations within its modifiers then first token on the line 215 * of the first token afer all annotations is return. This might be 216 * an annotation. 217 * Otherwise, the received <code>DetailAST</code> is returned. 218 * @param ast <code>DetailAST</code>. 219 * @return <code>DetailAST</code>. 220 */ 221 private DetailAST skipAnnotationOnlyLines(DetailAST ast) 222 { 223 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 224 if (modifiers == null) { 225 return ast; 226 } 227 DetailAST lastAnnot = findLastAnnotation(modifiers); 228 if (lastAnnot == null) { 229 // There are no annotations. 230 return ast; 231 } 232 final DetailAST tokenAfterLast = lastAnnot.getNextSibling() != null 233 ? lastAnnot.getNextSibling() 234 : modifiers.getNextSibling(); 235 if (tokenAfterLast.getLineNo() > lastAnnot.getLineNo()) { 236 return tokenAfterLast; 237 } 238 final int lastAnnotLineNumber = lastAnnot.getLineNo(); 239 while (lastAnnot.getPreviousSibling() != null 240 && (lastAnnot.getPreviousSibling().getLineNo() 241 == lastAnnotLineNumber)) 242 { 243 lastAnnot = lastAnnot.getPreviousSibling(); 244 } 245 return lastAnnot; 246 } 247 248 /** 249 * Find the last token of type <code>TokenTypes.ANNOTATION</code> 250 * under the given set of modifiers. 251 * @param modifiers <code>DetailAST</code>. 252 * @return <code>DetailAST</code> or null if there are no annotations. 253 */ 254 private DetailAST findLastAnnotation(DetailAST modifiers) 255 { 256 DetailAST annot = modifiers.findFirstToken(TokenTypes.ANNOTATION); 257 while (annot != null && annot.getNextSibling() != null 258 && annot.getNextSibling().getType() == TokenTypes.ANNOTATION) 259 { 260 annot = annot.getNextSibling(); 261 } 262 return annot; 263 } 264 265 /** 266 * Verifies that a specified left curly brace is placed correctly 267 * according to policy. 268 * @param brace token for left curly brace 269 * @param startToken token for start of expression 270 */ 271 private void verifyBrace(final DetailAST brace, 272 final DetailAST startToken) 273 { 274 final String braceLine = getLine(brace.getLineNo() - 1); 275 276 // calculate the previous line length without trailing whitespace. Need 277 // to handle the case where there is no previous line, cause the line 278 // being check is the first line in the file. 279 final int prevLineLen = (brace.getLineNo() == 1) 280 ? maxLineLength 281 : Utils.lengthMinusTrailingWhitespace(getLine(brace.getLineNo() - 2)); 282 283 // Check for being told to ignore, or have '{}' which is a special case 284 if ((braceLine.length() > (brace.getColumnNo() + 1)) 285 && (braceLine.charAt(brace.getColumnNo() + 1) == '}')) 286 { 287 ; // ignore 288 } 289 else if (getAbstractOption() == LeftCurlyOption.NL) { 290 if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) { 291 log(brace.getLineNo(), brace.getColumnNo(), 292 MSG_KEY_LINE_NEW, "{"); 293 } 294 } 295 else if (getAbstractOption() == LeftCurlyOption.EOL) { 296 if (Utils.whitespaceBefore(brace.getColumnNo(), braceLine) 297 && ((prevLineLen + 2) <= maxLineLength)) 298 { 299 log(brace.getLineNo(), brace.getColumnNo(), 300 MSG_KEY_LINE_PREVIOUS, "{"); 301 } 302 if (!hasLineBreakAfter(brace)) { 303 log(brace.getLineNo(), brace.getColumnNo(), MSG_KEY_LINE_BREAK_AFTER); 304 } 305 } 306 else if (getAbstractOption() == LeftCurlyOption.NLOW) { 307 if (startToken.getLineNo() == brace.getLineNo()) { 308 ; // all ok as on the same line 309 } 310 else if ((startToken.getLineNo() + 1) == brace.getLineNo()) { 311 if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) { 312 log(brace.getLineNo(), brace.getColumnNo(), 313 MSG_KEY_LINE_NEW, "{"); 314 } 315 else if ((prevLineLen + 2) <= maxLineLength) { 316 log(brace.getLineNo(), brace.getColumnNo(), 317 MSG_KEY_LINE_PREVIOUS, "{"); 318 } 319 } 320 else if (!Utils.whitespaceBefore(brace.getColumnNo(), braceLine)) { 321 log(brace.getLineNo(), brace.getColumnNo(), 322 MSG_KEY_LINE_NEW, "{"); 323 } 324 } 325 } 326 327 /** 328 * Checks if left curly has line break after. 329 * @param leftCurly 330 * Left curly token. 331 * @return 332 * True, left curly has line break after. 333 */ 334 private boolean hasLineBreakAfter(DetailAST leftCurly) 335 { 336 DetailAST nextToken = null; 337 if (leftCurly.getType() == TokenTypes.SLIST) { 338 nextToken = leftCurly.getFirstChild(); 339 } 340 else { 341 if (leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) 342 { 343 if (!ignoreEnums) { 344 nextToken = leftCurly.getNextSibling(); 345 } 346 } 347 } 348 if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) { 349 if (leftCurly.getLineNo() == nextToken.getLineNo()) { 350 return false; 351 } 352 } 353 return true; 354 } 355}