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