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