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.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 031 032/** 033 * <p> 034 * Checks the placement of left curly braces on types, methods and 035 * other blocks: 036 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, {@link 037 * TokenTypes#LITERAL_DO LITERAL_DO}, {@link TokenTypes#LITERAL_ELSE 038 * LITERAL_ELSE}, {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, {@link 039 * TokenTypes#LITERAL_FOR LITERAL_FOR}, {@link TokenTypes#LITERAL_IF 040 * LITERAL_IF}, {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, {@link 041 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, {@link 042 * TokenTypes#LITERAL_TRY LITERAL_TRY}, {@link TokenTypes#LITERAL_WHILE 043 * LITERAL_WHILE}, {@link TokenTypes#STATIC_INIT STATIC_INIT}. 044 * </p> 045 * 046 * <p> 047 * The policy to verify is specified using the {@link LeftCurlyOption} class and 048 * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL} 049 * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength. 050 * The default value for maxLineLength is 80. 051 * </p> 052 * <p> 053 * An example of how to configure the check is: 054 * </p> 055 * <pre> 056 * <module name="LeftCurly"/> 057 * </pre> 058 * <p> 059 * An example of how to configure the check with policy 060 * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is: 061 * </p> 062 * <pre> 063 * <module name="LeftCurly"> 064 * <property name="option" 065 * value="nlow"/> <property name="maxLineLength" value="120"/> < 066 * /module> 067 * </pre> 068 * <p> 069 * An example of how to configure the check to validate enum definitions: 070 * </p> 071 * <pre> 072 * <module name="LeftCurly"> 073 * <property name="ignoreEnums" value="false"/> 074 * </module> 075 * </pre> 076 * 077 * @author Oliver Burn 078 * @author lkuehne 079 * @author maxvetrenko 080 */ 081public class LeftCurlyCheck 082 extends Check { 083 /** 084 * A key is pointing to the warning message text in "messages.properties" 085 * file. 086 */ 087 public static final String MSG_KEY_LINE_NEW = "line.new"; 088 089 /** 090 * A key is pointing to the warning message text in "messages.properties" 091 * file. 092 */ 093 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous"; 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 100 101 /** Open curly brace literal. */ 102 private static final String OPEN_CURLY_BRACE = "{"; 103 104 /** If true, Check will ignore enums. */ 105 private boolean ignoreEnums = true; 106 107 /** The policy to enforce. */ 108 private LeftCurlyOption option = LeftCurlyOption.EOL; 109 110 /** 111 * Set the option to enforce. 112 * @param optionStr string to decode option from 113 * @throws ConversionException if unable to decode 114 */ 115 public void setOption(String optionStr) { 116 try { 117 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 118 } 119 catch (IllegalArgumentException iae) { 120 throw new ConversionException("unable to parse " + optionStr, iae); 121 } 122 } 123 124 /** 125 * Sets the maximum line length used in calculating the placement of the 126 * left curly brace. 127 * @param maxLineLength the max allowed line length 128 * @deprecated since 6.10 release, option is not required for the Check. 129 */ 130 @Deprecated 131 public void setMaxLineLength(int maxLineLength) { 132 // do nothing, option is deprecated 133 } 134 135 /** 136 * Sets whether check should ignore enums when left curly brace policy is EOL. 137 * @param ignoreEnums check's option for ignoring enums. 138 */ 139 public void setIgnoreEnums(boolean ignoreEnums) { 140 this.ignoreEnums = ignoreEnums; 141 } 142 143 @Override 144 public int[] getDefaultTokens() { 145 return getAcceptableTokens(); 146 } 147 148 @Override 149 public int[] getAcceptableTokens() { 150 return new int[] { 151 TokenTypes.INTERFACE_DEF, 152 TokenTypes.CLASS_DEF, 153 TokenTypes.ANNOTATION_DEF, 154 TokenTypes.ENUM_DEF, 155 TokenTypes.CTOR_DEF, 156 TokenTypes.METHOD_DEF, 157 TokenTypes.ENUM_CONSTANT_DEF, 158 TokenTypes.LITERAL_WHILE, 159 TokenTypes.LITERAL_TRY, 160 TokenTypes.LITERAL_CATCH, 161 TokenTypes.LITERAL_FINALLY, 162 TokenTypes.LITERAL_SYNCHRONIZED, 163 TokenTypes.LITERAL_SWITCH, 164 TokenTypes.LITERAL_DO, 165 TokenTypes.LITERAL_IF, 166 TokenTypes.LITERAL_ELSE, 167 TokenTypes.LITERAL_FOR, 168 TokenTypes.STATIC_INIT, 169 TokenTypes.OBJBLOCK, 170 }; 171 } 172 173 @Override 174 public int[] getRequiredTokens() { 175 return ArrayUtils.EMPTY_INT_ARRAY; 176 } 177 178 @Override 179 public void visitToken(DetailAST ast) { 180 DetailAST startToken; 181 DetailAST brace; 182 183 switch (ast.getType()) { 184 case TokenTypes.CTOR_DEF: 185 case TokenTypes.METHOD_DEF: 186 startToken = skipAnnotationOnlyLines(ast); 187 brace = ast.findFirstToken(TokenTypes.SLIST); 188 break; 189 case TokenTypes.INTERFACE_DEF: 190 case TokenTypes.CLASS_DEF: 191 case TokenTypes.ANNOTATION_DEF: 192 case TokenTypes.ENUM_DEF: 193 case TokenTypes.ENUM_CONSTANT_DEF: 194 startToken = skipAnnotationOnlyLines(ast); 195 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 196 brace = objBlock; 197 198 if (objBlock != null) { 199 brace = objBlock.getFirstChild(); 200 } 201 break; 202 case TokenTypes.LITERAL_WHILE: 203 case TokenTypes.LITERAL_CATCH: 204 case TokenTypes.LITERAL_SYNCHRONIZED: 205 case TokenTypes.LITERAL_FOR: 206 case TokenTypes.LITERAL_TRY: 207 case TokenTypes.LITERAL_FINALLY: 208 case TokenTypes.LITERAL_DO: 209 case TokenTypes.LITERAL_IF: 210 case TokenTypes.STATIC_INIT: 211 startToken = ast; 212 brace = ast.findFirstToken(TokenTypes.SLIST); 213 break; 214 case TokenTypes.LITERAL_ELSE: 215 startToken = ast; 216 final DetailAST candidate = ast.getFirstChild(); 217 brace = null; 218 219 if (candidate.getType() == TokenTypes.SLIST) { 220 brace = candidate; 221 } 222 break; 223 default: 224 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF, 225 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only. 226 // It has been done to improve coverage to 100%. I couldn't replace it with 227 // if-else-if block because code was ugly and didn't pass pmd check. 228 229 startToken = ast; 230 brace = ast.findFirstToken(TokenTypes.LCURLY); 231 break; 232 } 233 234 if (brace != null) { 235 verifyBrace(brace, startToken); 236 } 237 } 238 239 /** 240 * Skip lines that only contain {@code TokenTypes.ANNOTATION}s. 241 * If the received {@code DetailAST} 242 * has annotations within its modifiers then first token on the line 243 * of the first token after all annotations is return. This might be 244 * an annotation. 245 * Otherwise, the received {@code DetailAST} is returned. 246 * @param ast {@code DetailAST}. 247 * @return {@code DetailAST}. 248 */ 249 private static DetailAST skipAnnotationOnlyLines(DetailAST ast) { 250 DetailAST resultNode = ast; 251 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 252 253 if (modifiers != null) { 254 final DetailAST lastAnnotation = findLastAnnotation(modifiers); 255 256 if (lastAnnotation != null) { 257 final DetailAST tokenAfterLast; 258 259 if (lastAnnotation.getNextSibling() == null) { 260 tokenAfterLast = modifiers.getNextSibling(); 261 } 262 else { 263 tokenAfterLast = lastAnnotation.getNextSibling(); 264 } 265 266 if (tokenAfterLast.getLineNo() > lastAnnotation.getLineNo()) { 267 resultNode = tokenAfterLast; 268 } 269 else { 270 resultNode = getFirstAnnotationOnSameLine(lastAnnotation); 271 } 272 } 273 } 274 return resultNode; 275 } 276 277 /** 278 * Returns first annotation on same line. 279 * @param annotation 280 * last annotation on the line 281 * @return first annotation on same line. 282 */ 283 private static DetailAST getFirstAnnotationOnSameLine(DetailAST annotation) { 284 DetailAST previousAnnotation = annotation; 285 final int lastAnnotationLineNumber = previousAnnotation.getLineNo(); 286 while (previousAnnotation.getPreviousSibling() != null 287 && previousAnnotation.getPreviousSibling().getLineNo() 288 == lastAnnotationLineNumber) { 289 290 previousAnnotation = previousAnnotation.getPreviousSibling(); 291 } 292 return previousAnnotation; 293 } 294 295 /** 296 * Find the last token of type {@code TokenTypes.ANNOTATION} 297 * under the given set of modifiers. 298 * @param modifiers {@code DetailAST}. 299 * @return {@code DetailAST} or null if there are no annotations. 300 */ 301 private static DetailAST findLastAnnotation(DetailAST modifiers) { 302 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION); 303 while (annotation != null && annotation.getNextSibling() != null 304 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) { 305 annotation = annotation.getNextSibling(); 306 } 307 return annotation; 308 } 309 310 /** 311 * Verifies that a specified left curly brace is placed correctly 312 * according to policy. 313 * @param brace token for left curly brace 314 * @param startToken token for start of expression 315 */ 316 private void verifyBrace(final DetailAST brace, 317 final DetailAST startToken) { 318 final String braceLine = getLine(brace.getLineNo() - 1); 319 320 // Check for being told to ignore, or have '{}' which is a special case 321 if (braceLine.length() <= brace.getColumnNo() + 1 322 || braceLine.charAt(brace.getColumnNo() + 1) != '}') { 323 if (option == LeftCurlyOption.NL) { 324 if (!CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 325 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 326 } 327 } 328 else if (option == LeftCurlyOption.EOL) { 329 330 validateEol(brace, braceLine); 331 } 332 else if (startToken.getLineNo() != brace.getLineNo()) { 333 334 validateNewLinePosition(brace, startToken, braceLine); 335 336 } 337 } 338 } 339 340 /** 341 * Validate EOL case. 342 * @param brace brace AST 343 * @param braceLine line content 344 */ 345 private void validateEol(DetailAST brace, String braceLine) { 346 if (CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 347 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 348 } 349 if (!hasLineBreakAfter(brace)) { 350 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 351 } 352 } 353 354 /** 355 * Validate token on new Line position. 356 * @param brace brace AST 357 * @param startToken start Token 358 * @param braceLine content of line with Brace 359 */ 360 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) { 361 // not on the same line 362 if (startToken.getLineNo() + 1 == brace.getLineNo()) { 363 if (CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 364 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 365 } 366 else { 367 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 368 } 369 } 370 else if (!CommonUtils.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 371 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 372 } 373 } 374 375 /** 376 * Checks if left curly has line break after. 377 * @param leftCurly 378 * Left curly token. 379 * @return 380 * True, left curly has line break after. 381 */ 382 private boolean hasLineBreakAfter(DetailAST leftCurly) { 383 DetailAST nextToken = null; 384 if (leftCurly.getType() == TokenTypes.SLIST) { 385 nextToken = leftCurly.getFirstChild(); 386 } 387 else { 388 if (leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF 389 && !ignoreEnums) { 390 nextToken = leftCurly.getNextSibling(); 391 } 392 } 393 return nextToken == null 394 || nextToken.getType() == TokenTypes.RCURLY 395 || leftCurly.getLineNo() != nextToken.getLineNo(); 396 } 397}