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