001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.indentation; 021 022import java.util.Arrays; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 027 028/** 029 * Abstract base class for all handlers. 030 * 031 * @author jrichard 032 */ 033public abstract class AbstractExpressionHandler { 034 /** 035 * The instance of {@code IndentationCheck} using this handler. 036 */ 037 private final IndentationCheck indentCheck; 038 039 /** The AST which is handled by this handler. */ 040 private final DetailAST mainAst; 041 042 /** Name used during output to user. */ 043 private final String typeName; 044 045 /** Containing AST handler. */ 046 private final AbstractExpressionHandler parent; 047 048 /** Indentation amount for this handler. */ 049 private IndentLevel indent; 050 051 /** 052 * Construct an instance of this handler with the given indentation check, 053 * name, abstract syntax tree, and parent handler. 054 * 055 * @param indentCheck the indentation check 056 * @param typeName the name of the handler 057 * @param expr the abstract syntax tree 058 * @param parent the parent handler 059 */ 060 protected AbstractExpressionHandler(IndentationCheck indentCheck, String typeName, 061 DetailAST expr, AbstractExpressionHandler parent) { 062 this.indentCheck = indentCheck; 063 this.typeName = typeName; 064 mainAst = expr; 065 this.parent = parent; 066 } 067 068 /** 069 * Check the indentation of the expression we are handling. 070 */ 071 public abstract void checkIndentation(); 072 073 /** 074 * Get the indentation amount for this handler. For performance reasons, 075 * this value is cached. The first time this method is called, the 076 * indentation amount is computed and stored. On further calls, the stored 077 * value is returned. 078 * 079 * @return the expected indentation amount 080 * @noinspection WeakerAccess 081 */ 082 public final IndentLevel getIndent() { 083 if (indent == null) { 084 indent = getIndentImpl(); 085 } 086 return indent; 087 } 088 089 /** 090 * Compute the indentation amount for this handler. 091 * 092 * @return the expected indentation amount 093 */ 094 protected IndentLevel getIndentImpl() { 095 return parent.getSuggestedChildIndent(this); 096 } 097 098 /** 099 * Indentation level suggested for a child element. Children don't have 100 * to respect this, but most do. 101 * 102 * @param child child AST (so suggestion level can differ based on child 103 * type) 104 * 105 * @return suggested indentation for child 106 * @noinspection WeakerAccess 107 */ 108 public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) { 109 return new IndentLevel(getIndent(), getBasicOffset()); 110 } 111 112 /** 113 * Log an indentation error. 114 * 115 * @param ast the expression that caused the error 116 * @param subtypeName the type of the expression 117 * @param actualIndent the actual indent level of the expression 118 */ 119 protected final void logError(DetailAST ast, String subtypeName, 120 int actualIndent) { 121 logError(ast, subtypeName, actualIndent, getIndent()); 122 } 123 124 /** 125 * Log an indentation error. 126 * 127 * @param ast the expression that caused the error 128 * @param subtypeName the type of the expression 129 * @param actualIndent the actual indent level of the expression 130 * @param expectedIndent the expected indent level of the expression 131 */ 132 protected final void logError(DetailAST ast, String subtypeName, 133 int actualIndent, IndentLevel expectedIndent) { 134 final String typeStr; 135 136 if (subtypeName.isEmpty()) { 137 typeStr = ""; 138 } 139 else { 140 typeStr = " " + subtypeName; 141 } 142 String messageKey = IndentationCheck.MSG_ERROR; 143 if (expectedIndent.isMultiLevel()) { 144 messageKey = IndentationCheck.MSG_ERROR_MULTI; 145 } 146 indentCheck.indentationLog(ast.getLineNo(), messageKey, 147 typeName + typeStr, actualIndent, expectedIndent); 148 } 149 150 /** 151 * Log child indentation error. 152 * 153 * @param line the expression that caused the error 154 * @param actualIndent the actual indent level of the expression 155 * @param expectedIndent the expected indent level of the expression 156 */ 157 private void logChildError(int line, 158 int actualIndent, 159 IndentLevel expectedIndent) { 160 String messageKey = IndentationCheck.MSG_CHILD_ERROR; 161 if (expectedIndent.isMultiLevel()) { 162 messageKey = IndentationCheck.MSG_CHILD_ERROR_MULTI; 163 } 164 indentCheck.indentationLog(line, messageKey, 165 typeName, actualIndent, expectedIndent); 166 } 167 168 /** 169 * Determines if the given expression is at the start of a line. 170 * 171 * @param ast the expression to check 172 * 173 * @return true if it is, false otherwise 174 */ 175 protected final boolean isOnStartOfLine(DetailAST ast) { 176 return getLineStart(ast) == expandedTabsColumnNo(ast); 177 } 178 179 /** 180 * Determines if two expressions are on the same line. 181 * 182 * @param ast1 the first expression 183 * @param ast2 the second expression 184 * 185 * @return true if they are, false otherwise 186 * @noinspection WeakerAccess 187 */ 188 public static boolean areOnSameLine(DetailAST ast1, DetailAST ast2) { 189 return ast1.getLineNo() == ast2.getLineNo(); 190 } 191 192 /** 193 * Searches in given sub-tree (including given node) for the token 194 * which represents first symbol for this sub-tree in file. 195 * @param ast a root of sub-tree in which the search should be performed. 196 * @return a token which occurs first in the file. 197 * @noinspection WeakerAccess 198 */ 199 public static DetailAST getFirstToken(DetailAST ast) { 200 DetailAST first = ast; 201 DetailAST child = ast.getFirstChild(); 202 203 while (child != null) { 204 final DetailAST toTest = getFirstToken(child); 205 if (toTest.getColumnNo() < first.getColumnNo()) { 206 first = toTest; 207 } 208 child = child.getNextSibling(); 209 } 210 211 return first; 212 } 213 214 /** 215 * Get the start of the line for the given expression. 216 * 217 * @param ast the expression to find the start of the line for 218 * 219 * @return the start of the line for the given expression 220 */ 221 protected final int getLineStart(DetailAST ast) { 222 return getLineStart(ast.getLineNo()); 223 } 224 225 /** 226 * Get the start of the line for the given line number. 227 * 228 * @param lineNo the line number to find the start for 229 * 230 * @return the start of the line for the given expression 231 */ 232 protected final int getLineStart(int lineNo) { 233 return getLineStart(indentCheck.getLine(lineNo - 1)); 234 } 235 236 /** 237 * Get the start of the specified line. 238 * 239 * @param line the specified line number 240 * 241 * @return the start of the specified line 242 */ 243 private int getLineStart(String line) { 244 int index = 0; 245 while (Character.isWhitespace(line.charAt(index))) { 246 index++; 247 } 248 return CommonUtils.lengthExpandedTabs( 249 line, index, indentCheck.getIndentationTabWidth()); 250 } 251 252 /** 253 * Checks that indentation should be increased after first line in checkLinesIndent(). 254 * @return true if indentation should be increased after 255 * first line in checkLinesIndent() 256 * false otherwise 257 */ 258 protected boolean shouldIncreaseIndent() { 259 return true; 260 } 261 262 /** 263 * Check the indentation for a set of lines. 264 * 265 * @param lines the set of lines to check 266 * @param indentLevel the indentation level 267 * @param firstLineMatches whether or not the first line has to match 268 * @param firstLine first line of whole expression 269 */ 270 private void checkLinesIndent(LineSet lines, 271 IndentLevel indentLevel, 272 boolean firstLineMatches, 273 int firstLine) { 274 if (!lines.isEmpty()) { 275 // check first line 276 final int startLine = lines.firstLine(); 277 final int endLine = lines.lastLine(); 278 final int startCol = lines.firstLineCol(); 279 280 final int realStartCol = 281 getLineStart(indentCheck.getLine(startLine - 1)); 282 283 if (realStartCol == startCol) { 284 checkLineIndent(startLine, startCol, indentLevel, 285 firstLineMatches); 286 } 287 288 // if first line starts the line, following lines are indented 289 // one level; but if the first line of this expression is 290 // nested with the previous expression (which is assumed if it 291 // doesn't start the line) then don't indent more, the first 292 // indentation is absorbed by the nesting 293 294 IndentLevel theLevel = indentLevel; 295 if (firstLineMatches 296 || firstLine > mainAst.getLineNo() && shouldIncreaseIndent()) { 297 theLevel = new IndentLevel(indentLevel, getBasicOffset()); 298 } 299 300 // check following lines 301 for (int i = startLine + 1; i <= endLine; i++) { 302 final Integer col = lines.getStartColumn(i); 303 // startCol could be null if this line didn't have an 304 // expression that was required to be checked (it could be 305 // checked by a child expression) 306 307 if (col != null) { 308 checkLineIndent(i, col, theLevel, false); 309 } 310 } 311 } 312 } 313 314 /** 315 * Check the indentation for a single line. 316 * 317 * @param lineNum the number of the line to check 318 * @param colNum the column number we are starting at 319 * @param indentLevel the indentation level 320 * @param mustMatch whether or not the indentation level must match 321 */ 322 private void checkLineIndent(int lineNum, int colNum, 323 IndentLevel indentLevel, boolean mustMatch) { 324 final String line = indentCheck.getLine(lineNum - 1); 325 final int start = getLineStart(line); 326 // if must match is set, it is an error if the line start is not 327 // at the correct indention level; otherwise, it is an only an 328 // error if this statement starts the line and it is less than 329 // the correct indentation level 330 if (mustMatch && !indentLevel.isAcceptable(start) 331 || !mustMatch && colNum == start && indentLevel.isGreaterThan(start)) { 332 logChildError(lineNum, start, indentLevel); 333 } 334 } 335 336 /** 337 * Checks indentation on wrapped lines between and including 338 * {@code firstNode} and {@code lastNode}. 339 * 340 * @param firstNode First node to start examining. 341 * @param lastNode Last node to examine inclusively. 342 */ 343 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) { 344 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode); 345 } 346 347 /** 348 * Checks indentation on wrapped lines between and including 349 * {@code firstNode} and {@code lastNode}. 350 * 351 * @param firstNode First node to start examining. 352 * @param lastNode Last node to examine inclusively. 353 * @param wrappedIndentLevel Indentation all wrapped lines should use. 354 * @param startIndent Indentation first line before wrapped lines used. 355 * @param ignoreFirstLine Test if first line's indentation should be checked or not. 356 */ 357 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode, 358 int wrappedIndentLevel, int startIndent, boolean ignoreFirstLine) { 359 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode, 360 wrappedIndentLevel, startIndent, 361 LineWrappingHandler.LineWrappingOptions.ofBoolean(ignoreFirstLine)); 362 } 363 364 /** 365 * Check the indent level of the children of the specified parent 366 * expression. 367 * 368 * @param parentNode the parent whose children we are checking 369 * @param tokenTypes the token types to check 370 * @param startIndent the starting indent level 371 * @param firstLineMatches whether or not the first line needs to match 372 * @param allowNesting whether or not nested children are allowed 373 */ 374 protected final void checkChildren(DetailAST parentNode, 375 int[] tokenTypes, 376 IndentLevel startIndent, 377 boolean firstLineMatches, 378 boolean allowNesting) { 379 Arrays.sort(tokenTypes); 380 for (DetailAST child = parentNode.getFirstChild(); 381 child != null; 382 child = child.getNextSibling()) { 383 if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0) { 384 checkExpressionSubtree(child, startIndent, 385 firstLineMatches, allowNesting); 386 } 387 } 388 } 389 390 /** 391 * Check the indentation level for an expression subtree. 392 * 393 * @param tree the expression subtree to check 394 * @param indentLevel the indentation level 395 * @param firstLineMatches whether or not the first line has to match 396 * @param allowNesting whether or not subtree nesting is allowed 397 */ 398 protected final void checkExpressionSubtree( 399 DetailAST tree, 400 IndentLevel indentLevel, 401 boolean firstLineMatches, 402 boolean allowNesting 403 ) { 404 final LineSet subtreeLines = new LineSet(); 405 final int firstLine = getFirstLine(Integer.MAX_VALUE, tree); 406 if (firstLineMatches && !allowNesting) { 407 subtreeLines.addLineAndCol(firstLine, 408 getLineStart(indentCheck.getLine(firstLine - 1))); 409 } 410 findSubtreeLines(subtreeLines, tree, allowNesting); 411 412 checkLinesIndent(subtreeLines, indentLevel, firstLineMatches, firstLine); 413 } 414 415 /** 416 * Get the first line for a given expression. 417 * 418 * @param startLine the line we are starting from 419 * @param tree the expression to find the first line for 420 * 421 * @return the first line of the expression 422 */ 423 protected static int getFirstLine(int startLine, DetailAST tree) { 424 int realStart = startLine; 425 final int currLine = tree.getLineNo(); 426 if (currLine < realStart) { 427 realStart = currLine; 428 } 429 430 // check children 431 for (DetailAST node = tree.getFirstChild(); 432 node != null; 433 node = node.getNextSibling()) { 434 realStart = getFirstLine(realStart, node); 435 } 436 437 return realStart; 438 } 439 440 /** 441 * Get the column number for the start of a given expression, expanding 442 * tabs out into spaces in the process. 443 * 444 * @param ast the expression to find the start of 445 * 446 * @return the column number for the start of the expression 447 */ 448 protected final int expandedTabsColumnNo(DetailAST ast) { 449 final String line = 450 indentCheck.getLine(ast.getLineNo() - 1); 451 452 return CommonUtils.lengthExpandedTabs(line, ast.getColumnNo(), 453 indentCheck.getIndentationTabWidth()); 454 } 455 456 /** 457 * Find the set of lines for a given subtree. 458 * 459 * @param lines the set of lines to add to 460 * @param tree the subtree to examine 461 * @param allowNesting whether or not to allow nested subtrees 462 */ 463 protected final void findSubtreeLines(LineSet lines, DetailAST tree, 464 boolean allowNesting) { 465 if (!indentCheck.getHandlerFactory().isHandledType(tree.getType())) { 466 final int lineNum = tree.getLineNo(); 467 final Integer colNum = lines.getStartColumn(lineNum); 468 469 final int thisLineColumn = expandedTabsColumnNo(tree); 470 if (colNum == null || thisLineColumn < colNum) { 471 lines.addLineAndCol(lineNum, thisLineColumn); 472 } 473 474 // check children 475 for (DetailAST node = tree.getFirstChild(); 476 node != null; 477 node = node.getNextSibling()) { 478 findSubtreeLines(lines, node, allowNesting); 479 } 480 } 481 } 482 483 /** 484 * Check the indentation level of modifiers. 485 */ 486 protected void checkModifiers() { 487 final DetailAST modifiers = 488 mainAst.findFirstToken(TokenTypes.MODIFIERS); 489 for (DetailAST modifier = modifiers.getFirstChild(); 490 modifier != null; 491 modifier = modifier.getNextSibling()) { 492 if (isOnStartOfLine(modifier) 493 && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) { 494 logError(modifier, "modifier", 495 expandedTabsColumnNo(modifier)); 496 } 497 } 498 } 499 500 /** 501 * Accessor for the IndentCheck attribute. 502 * 503 * @return the IndentCheck attribute 504 */ 505 protected final IndentationCheck getIndentCheck() { 506 return indentCheck; 507 } 508 509 /** 510 * Accessor for the MainAst attribute. 511 * 512 * @return the MainAst attribute 513 */ 514 protected final DetailAST getMainAst() { 515 return mainAst; 516 } 517 518 /** 519 * Accessor for the Parent attribute. 520 * 521 * @return the Parent attribute 522 */ 523 protected final AbstractExpressionHandler getParent() { 524 return parent; 525 } 526 527 /** 528 * A shortcut for {@code IndentationCheck} property. 529 * @return value of basicOffset property of {@code IndentationCheck} 530 */ 531 protected final int getBasicOffset() { 532 return indentCheck.getBasicOffset(); 533 } 534 535 /** 536 * A shortcut for {@code IndentationCheck} property. 537 * @return value of braceAdjustment property 538 * of {@code IndentationCheck} 539 */ 540 protected final int getBraceAdjustment() { 541 return indentCheck.getBraceAdjustment(); 542 } 543 544 /** 545 * Check the indentation of the right parenthesis. 546 * @param rparen parenthesis to check 547 * @param lparen left parenthesis associated with aRparen 548 */ 549 protected final void checkRightParen(DetailAST lparen, DetailAST rparen) { 550 if (rparen != null) { 551 // the rcurly can either be at the correct indentation, 552 // or not first on the line 553 final int rparenLevel = expandedTabsColumnNo(rparen); 554 // or has <lparen level> + 1 indentation 555 final int lparenLevel = expandedTabsColumnNo(lparen); 556 557 if (rparenLevel != lparenLevel + 1 558 && !getIndent().isAcceptable(rparenLevel) 559 && isOnStartOfLine(rparen)) { 560 logError(rparen, "rparen", rparenLevel); 561 } 562 } 563 } 564 565 /** 566 * Check the indentation of the left parenthesis. 567 * @param lparen parenthesis to check 568 */ 569 protected final void checkLeftParen(final DetailAST lparen) { 570 // the rcurly can either be at the correct indentation, or on the 571 // same line as the lcurly 572 if (lparen != null 573 && !getIndent().isAcceptable(expandedTabsColumnNo(lparen)) 574 && isOnStartOfLine(lparen)) { 575 logError(lparen, "lparen", expandedTabsColumnNo(lparen)); 576 } 577 } 578}