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