001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 final String line = indentCheck.getLine(ast.getLineNo() - 1); 219 return getLineStart(line); 220 } 221 222 /** 223 * Get the start of the specified line. 224 * 225 * @param line the specified line number 226 * 227 * @return the start of the specified line 228 */ 229 protected final int getLineStart(String line) { 230 int index = 0; 231 while (index < line.length() && Character.isWhitespace(line.charAt(index))) { 232 index++; 233 } 234 return CommonUtils.lengthExpandedTabs( 235 line, index, indentCheck.getIndentationTabWidth()); 236 } 237 238 /** 239 * Checks that indentation should be increased after first line in checkLinesIndent(). 240 * @return true if indentation should be increased after 241 * first line in checkLinesIndent() 242 * false otherwise 243 */ 244 protected boolean shouldIncreaseIndent() { 245 return true; 246 } 247 248 /** 249 * Check the indentation of consecutive lines for the expression we are 250 * handling. 251 * 252 * @param startLine the first line to check 253 * @param endLine the last line to check 254 * @param indentLevel the required indent level 255 */ 256 protected final void checkLinesIndent(int startLine, int endLine, 257 IndentLevel indentLevel) { 258 // check first line 259 checkLineIndent(startLine, indentLevel); 260 261 // check following lines 262 final IndentLevel offsetLevel = 263 new IndentLevel(indentLevel, getBasicOffset()); 264 for (int i = startLine + 1; i <= endLine; i++) { 265 checkLineIndent(i, offsetLevel); 266 } 267 } 268 269 /** 270 * Check the indentation for a set of lines. 271 * 272 * @param lines the set of lines to check 273 * @param indentLevel the indentation level 274 * @param firstLineMatches whether or not the first line has to match 275 * @param firstLine first line of whole expression 276 */ 277 private void checkLinesIndent(LineSet lines, 278 IndentLevel indentLevel, 279 boolean firstLineMatches, 280 int firstLine) { 281 if (lines.isEmpty()) { 282 return; 283 } 284 285 // check first line 286 final int startLine = lines.firstLine(); 287 final int endLine = lines.lastLine(); 288 final int startCol = lines.firstLineCol(); 289 290 final int realStartCol = 291 getLineStart(indentCheck.getLine(startLine - 1)); 292 293 if (realStartCol == startCol) { 294 checkLineIndent(startLine, startCol, indentLevel, 295 firstLineMatches); 296 } 297 298 // if first line starts the line, following lines are indented 299 // one level; but if the first line of this expression is 300 // nested with the previous expression (which is assumed if it 301 // doesn't start the line) then don't indent more, the first 302 // indentation is absorbed by the nesting 303 304 IndentLevel theLevel = indentLevel; 305 if (firstLineMatches 306 || firstLine > mainAst.getLineNo() && shouldIncreaseIndent()) { 307 theLevel = new IndentLevel(indentLevel, getBasicOffset()); 308 } 309 310 // check following lines 311 for (int i = startLine + 1; i <= endLine; i++) { 312 final Integer col = lines.getStartColumn(i); 313 // startCol could be null if this line didn't have an 314 // expression that was required to be checked (it could be 315 // checked by a child expression) 316 317 if (col != null) { 318 checkLineIndent(i, col, theLevel, false); 319 } 320 } 321 } 322 323 /** 324 * Check the indent level for a single line. 325 * 326 * @param lineNum the line number to check 327 * @param indentLevel the required indent level 328 */ 329 private void checkLineIndent(int lineNum, IndentLevel indentLevel) { 330 final String line = indentCheck.getLine(lineNum - 1); 331 if (!line.isEmpty()) { 332 final int start = getLineStart(line); 333 if (indentLevel.isGreaterThan(start)) { 334 logChildError(lineNum, start, indentLevel); 335 } 336 } 337 } 338 339 /** 340 * Check the indentation for a single line. 341 * 342 * @param lineNum the number of the line to check 343 * @param colNum the column number we are starting at 344 * @param indentLevel the indentation level 345 * @param mustMatch whether or not the indentation level must match 346 */ 347 private void checkLineIndent(int lineNum, int colNum, 348 IndentLevel indentLevel, boolean mustMatch) { 349 final String line = indentCheck.getLine(lineNum - 1); 350 final int start = getLineStart(line); 351 // if must match is set, it is an error if the line start is not 352 // at the correct indention level; otherwise, it is an only an 353 // error if this statement starts the line and it is less than 354 // the correct indentation level 355 if (mustMatch && !indentLevel.isAcceptable(start) 356 || !mustMatch && colNum == start && indentLevel.isGreaterThan(start)) { 357 logChildError(lineNum, start, indentLevel); 358 } 359 } 360 361 /** 362 * Checks indentation on wrapped lines between and including 363 * {@code firstNode} and {@code lastNode}. 364 * 365 * @param firstNode First node to start examining. 366 * @param lastNode Last node to examine inclusively. 367 */ 368 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) { 369 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode); 370 } 371 372 /** 373 * Check the indent level of the children of the specified parent 374 * expression. 375 * 376 * @param parentNode the parent whose children we are checking 377 * @param tokenTypes the token types to check 378 * @param startIndent the starting indent level 379 * @param firstLineMatches whether or not the first line needs to match 380 * @param allowNesting whether or not nested children are allowed 381 */ 382 protected final void checkChildren(DetailAST parentNode, 383 int[] tokenTypes, 384 IndentLevel startIndent, 385 boolean firstLineMatches, 386 boolean allowNesting) { 387 Arrays.sort(tokenTypes); 388 for (DetailAST child = parentNode.getFirstChild(); 389 child != null; 390 child = child.getNextSibling()) { 391 if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0) { 392 checkExpressionSubtree(child, startIndent, 393 firstLineMatches, allowNesting); 394 } 395 } 396 } 397 398 /** 399 * Check the indentation level for an expression subtree. 400 * 401 * @param tree the expression subtree to check 402 * @param indentLevel the indentation level 403 * @param firstLineMatches whether or not the first line has to match 404 * @param allowNesting whether or not subtree nesting is allowed 405 */ 406 protected final void checkExpressionSubtree( 407 DetailAST tree, 408 IndentLevel indentLevel, 409 boolean firstLineMatches, 410 boolean allowNesting 411 ) { 412 final LineSet subtreeLines = new LineSet(); 413 final int firstLine = getFirstLine(Integer.MAX_VALUE, tree); 414 if (firstLineMatches && !allowNesting) { 415 subtreeLines.addLineAndCol(firstLine, 416 getLineStart(indentCheck.getLine(firstLine - 1))); 417 } 418 findSubtreeLines(subtreeLines, tree, allowNesting); 419 420 checkLinesIndent(subtreeLines, indentLevel, firstLineMatches, firstLine); 421 } 422 423 /** 424 * Get the first line for a given expression. 425 * 426 * @param startLine the line we are starting from 427 * @param tree the expression to find the first line for 428 * 429 * @return the first line of the expression 430 */ 431 protected final int getFirstLine(int startLine, DetailAST tree) { 432 int realStart = startLine; 433 final int currLine = tree.getLineNo(); 434 if (currLine < realStart) { 435 realStart = currLine; 436 } 437 438 // check children 439 for (DetailAST node = tree.getFirstChild(); 440 node != null; 441 node = node.getNextSibling()) { 442 realStart = getFirstLine(realStart, node); 443 } 444 445 return realStart; 446 } 447 448 /** 449 * Get the column number for the start of a given expression, expanding 450 * tabs out into spaces in the process. 451 * 452 * @param ast the expression to find the start of 453 * 454 * @return the column number for the start of the expression 455 */ 456 protected final int expandedTabsColumnNo(DetailAST ast) { 457 final String line = 458 indentCheck.getLine(ast.getLineNo() - 1); 459 460 return CommonUtils.lengthExpandedTabs(line, ast.getColumnNo(), 461 indentCheck.getIndentationTabWidth()); 462 } 463 464 /** 465 * Find the set of lines for a given subtree. 466 * 467 * @param lines the set of lines to add to 468 * @param tree the subtree to examine 469 * @param allowNesting whether or not to allow nested subtrees 470 */ 471 protected final void findSubtreeLines(LineSet lines, DetailAST tree, 472 boolean allowNesting) { 473 if (indentCheck.getHandlerFactory().isHandledType(tree.getType())) { 474 return; 475 } 476 477 final int lineNum = tree.getLineNo(); 478 final Integer colNum = lines.getStartColumn(lineNum); 479 480 final int thisLineColumn = expandedTabsColumnNo(tree); 481 if (colNum == null || thisLineColumn < colNum) { 482 lines.addLineAndCol(lineNum, thisLineColumn); 483 } 484 485 // check children 486 for (DetailAST node = tree.getFirstChild(); 487 node != null; 488 node = node.getNextSibling()) { 489 findSubtreeLines(lines, node, allowNesting); 490 } 491 } 492 493 /** 494 * Check the indentation level of modifiers. 495 */ 496 protected void checkModifiers() { 497 final DetailAST modifiers = 498 mainAst.findFirstToken(TokenTypes.MODIFIERS); 499 for (DetailAST modifier = modifiers.getFirstChild(); 500 modifier != null; 501 modifier = modifier.getNextSibling()) { 502 if (isOnStartOfLine(modifier) 503 && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) { 504 logError(modifier, "modifier", 505 expandedTabsColumnNo(modifier)); 506 } 507 } 508 } 509 510 /** 511 * Accessor for the IndentCheck attribute. 512 * 513 * @return the IndentCheck attribute 514 */ 515 protected final IndentationCheck getIndentCheck() { 516 return indentCheck; 517 } 518 519 /** 520 * Accessor for the MainAst attribute. 521 * 522 * @return the MainAst attribute 523 */ 524 protected final DetailAST getMainAst() { 525 return mainAst; 526 } 527 528 /** 529 * Accessor for the Parent attribute. 530 * 531 * @return the Parent attribute 532 */ 533 protected final AbstractExpressionHandler getParent() { 534 return parent; 535 } 536 537 /** 538 * A shortcut for {@code IndentationCheck} property. 539 * @return value of basicOffset property of {@code IndentationCheck} 540 */ 541 protected final int getBasicOffset() { 542 return indentCheck.getBasicOffset(); 543 } 544 545 /** 546 * A shortcut for {@code IndentationCheck} property. 547 * @return value of braceAdjustment property 548 * of {@code IndentationCheck} 549 */ 550 protected final int getBraceAdjustment() { 551 return indentCheck.getBraceAdjustment(); 552 } 553 554 /** 555 * Check the indentation of the right parenthesis. 556 * @param rparen parenthesis to check 557 * @param lparen left parenthesis associated with aRparen 558 */ 559 protected final void checkRParen(DetailAST lparen, DetailAST rparen) { 560 if (rparen != null) { 561 // the rcurly can either be at the correct indentation, 562 // or not first on the line 563 final int rparenLevel = expandedTabsColumnNo(rparen); 564 // or has <lparen level> + 1 indentation 565 final int lparenLevel = expandedTabsColumnNo(lparen); 566 567 if (rparenLevel != lparenLevel + 1 568 && !getIndent().isAcceptable(rparenLevel) 569 && isOnStartOfLine(rparen)) { 570 logError(rparen, "rparen", rparenLevel); 571 } 572 } 573 } 574 575 /** 576 * Check the indentation of the left parenthesis. 577 * @param lparen parenthesis to check 578 */ 579 protected final void checkLParen(final DetailAST lparen) { 580 // the rcurly can either be at the correct indentation, or on the 581 // same line as the lcurly 582 if (lparen == null 583 || getIndent().isAcceptable(expandedTabsColumnNo(lparen)) 584 || !isOnStartOfLine(lparen)) { 585 return; 586 } 587 logError(lparen, "lparen", expandedTabsColumnNo(lparen)); 588 } 589}