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