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