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