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