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