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 mIndentCheck; 037 038 /** the AST which is handled by this handler */ 039 private final DetailAST mMainAst; 040 041 /** name used during output to user */ 042 private final String mTypeName; 043 044 /** containing AST handler */ 045 private final ExpressionHandler mParent; 046 047 /** indentation amount for this handler */ 048 private IndentLevel mLevel; 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 aIndentCheck the indentation check 055 * @param aTypeName the name of the handler 056 * @param aExpr the abstract syntax tree 057 * @param aParent the parent handler 058 */ 059 public ExpressionHandler(IndentationCheck aIndentCheck, 060 String aTypeName, DetailAST aExpr, ExpressionHandler aParent) 061 { 062 mIndentCheck = aIndentCheck; 063 mTypeName = aTypeName; 064 mMainAst = aExpr; 065 mParent = aParent; 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 (mLevel == null) { 079 mLevel = getLevelImpl(); 080 } 081 return mLevel; 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 mParent.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 aChild 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 aChild) 104 { 105 return new IndentLevel(getLevel(), getBasicOffset()); 106 } 107 108 /** 109 * Log an indentation error. 110 * 111 * @param aAst the expression that caused the error 112 * @param aSubtypeName the type of the expression 113 * @param aActualLevel the actual indent level of the expression 114 */ 115 protected final void logError(DetailAST aAst, String aSubtypeName, 116 int aActualLevel) 117 { 118 logError(aAst, aSubtypeName, aActualLevel, getLevel()); 119 } 120 121 /** 122 * Log an indentation error. 123 * 124 * @param aAst the expression that caused the error 125 * @param aSubtypeName the type of the expression 126 * @param aActualLevel the actual indent level of the expression 127 * @param aExpectedLevel the expected indent level of the expression 128 */ 129 protected final void logError(DetailAST aAst, String aSubtypeName, 130 int aActualLevel, IndentLevel aExpectedLevel) 131 { 132 final String typeStr = 133 ("".equals(aSubtypeName) ? "" : (" " + aSubtypeName)); 134 String messageKey = "indentation.error"; 135 if (aExpectedLevel.isMultiLevel()) { 136 messageKey = "indentation.error.multi"; 137 } 138 mIndentCheck.indentationLog(aAst.getLineNo(), messageKey, 139 mTypeName + typeStr, aActualLevel, aExpectedLevel); 140 } 141 142 /** 143 * Log child indentation error. 144 * 145 * @param aLine the expression that caused the error 146 * @param aActualLevel the actual indent level of the expression 147 * @param aExpectedLevel the expected indent level of the expression 148 */ 149 private void logChildError(int aLine, 150 int aActualLevel, 151 IndentLevel aExpectedLevel) 152 { 153 String messageKey = "indentation.child.error"; 154 if (aExpectedLevel.isMultiLevel()) { 155 messageKey = "indentation.child.error.multi"; 156 } 157 mIndentCheck.indentationLog(aLine, messageKey, 158 mTypeName, aActualLevel, aExpectedLevel); 159 } 160 161 /** 162 * Determines if the given expression is at the start of a line. 163 * 164 * @param aAst the expression to check 165 * 166 * @return true if it is, false otherwise 167 */ 168 protected final boolean startsLine(DetailAST aAst) 169 { 170 return getLineStart(aAst) == expandedTabsColumnNo(aAst); 171 } 172 173 /** 174 * Determines if two expressions are on the same line. 175 * 176 * @param aAst1 the first expression 177 * @param aAst2 the second expression 178 * 179 * @return true if they are, false otherwise 180 */ 181 static boolean areOnSameLine(DetailAST aAst1, DetailAST aAst2) 182 { 183 return (aAst1 != null) && (aAst2 != null) 184 && (aAst1.getLineNo() == aAst2.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 aAST 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 aAST) 194 { 195 DetailAST first = aAST; 196 DetailAST child = aAST.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 aAst 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 aAst) 220 { 221 final String line = mIndentCheck.getLines()[aAst.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 aStartLine the first line to check 230 * @param aEndLine the last line to check 231 * @param aIndentLevel the required indent level 232 */ 233 protected final void checkLinesIndent(int aStartLine, int aEndLine, 234 IndentLevel aIndentLevel) 235 { 236 // check first line 237 checkSingleLine(aStartLine, aIndentLevel); 238 239 // check following lines 240 final IndentLevel offsetLevel = 241 new IndentLevel(aIndentLevel, getBasicOffset()); 242 for (int i = aStartLine + 1; i <= aEndLine; 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 aLines the set of lines to check 261 * @param aIndentLevel the indentation level 262 * @param aFirstLineMatches whether or not the first line has to match 263 * @param aFirstLine firstline of whole expression 264 */ 265 private void checkLinesIndent(LineSet aLines, 266 IndentLevel aIndentLevel, 267 boolean aFirstLineMatches, 268 int aFirstLine) 269 { 270 if (aLines.isEmpty()) { 271 return; 272 } 273 274 // check first line 275 final int startLine = aLines.firstLine(); 276 final int endLine = aLines.lastLine(); 277 final int startCol = aLines.firstLineCol(); 278 279 final int realStartCol = 280 getLineStart(mIndentCheck.getLines()[startLine - 1]); 281 282 if (realStartCol == startCol) { 283 checkSingleLine(startLine, startCol, aIndentLevel, 284 aFirstLineMatches); 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 = aIndentLevel; 294 if (aFirstLineMatches 295 || ((aFirstLine > mMainAst.getLineNo()) && shouldIncreaseIndent())) 296 { 297 theLevel = new IndentLevel(aIndentLevel, getBasicOffset()); 298 } 299 300 // check following lines 301 for (int i = startLine + 1; i <= endLine; i++) { 302 final Integer col = aLines.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 aLineNum the line number to check 317 * @param aIndentLevel the required indent level 318 */ 319 private void checkSingleLine(int aLineNum, IndentLevel aIndentLevel) 320 { 321 final String line = mIndentCheck.getLines()[aLineNum - 1]; 322 final int start = getLineStart(line); 323 if (aIndentLevel.gt(start)) { 324 logChildError(aLineNum, start, aIndentLevel); 325 } 326 } 327 328 /** 329 * Check the indentation for a single line. 330 * 331 * @param aLineNum the number of the line to check 332 * @param aColNum the column number we are starting at 333 * @param aIndentLevel the indentation level 334 * @param aMustMatch whether or not the indentation level must match 335 */ 336 337 private void checkSingleLine(int aLineNum, int aColNum, 338 IndentLevel aIndentLevel, boolean aMustMatch) 339 { 340 final String line = mIndentCheck.getLines()[aLineNum - 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 (aMustMatch ? !aIndentLevel.accept(start) 347 : (aColNum == start) && aIndentLevel.gt(start)) 348 { 349 logChildError(aLineNum, start, aIndentLevel); 350 } 351 } 352 353 /** 354 * Get the start of the specified line. 355 * 356 * @param aLine the specified line number 357 * 358 * @return the start of the specified line 359 */ 360 protected final int getLineStart(String aLine) 361 { 362 for (int start = 0; start < aLine.length(); start++) { 363 final char c = aLine.charAt(start); 364 365 if (!Character.isWhitespace(c)) { 366 return Utils.lengthExpandedTabs( 367 aLine, start, mIndentCheck.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 aParent the parent whose children we are checking 378 * @param aTokenTypes the token types to check 379 * @param aStartLevel the starting indent level 380 * @param aFirstLineMatches whether or not the first line needs to match 381 * @param aAllowNesting whether or not nested children are allowed 382 */ 383 protected final void checkChildren(DetailAST aParent, 384 int[] aTokenTypes, 385 IndentLevel aStartLevel, 386 boolean aFirstLineMatches, 387 boolean aAllowNesting) 388 { 389 Arrays.sort(aTokenTypes); 390 for (DetailAST child = aParent.getFirstChild(); 391 child != null; 392 child = child.getNextSibling()) 393 { 394 if (Arrays.binarySearch(aTokenTypes, child.getType()) >= 0) { 395 checkExpressionSubtree(child, aStartLevel, 396 aFirstLineMatches, aAllowNesting); 397 } 398 } 399 } 400 401 /** 402 * Check the indentation level for an expression subtree. 403 * 404 * @param aTree the expression subtree to check 405 * @param aLevel the indentation level 406 * @param aFirstLineMatches whether or not the first line has to match 407 * @param aAllowNesting whether or not subtree nesting is allowed 408 */ 409 protected final void checkExpressionSubtree( 410 DetailAST aTree, 411 IndentLevel aLevel, 412 boolean aFirstLineMatches, 413 boolean aAllowNesting 414 ) 415 { 416 final LineSet subtreeLines = new LineSet(); 417 final int firstLine = getFirstLine(Integer.MAX_VALUE, aTree); 418 if (aFirstLineMatches && !aAllowNesting) { 419 subtreeLines.addLineAndCol(firstLine, 420 getLineStart(mIndentCheck.getLines()[firstLine - 1])); 421 } 422 findSubtreeLines(subtreeLines, aTree, aAllowNesting); 423 424 checkLinesIndent(subtreeLines, aLevel, aFirstLineMatches, firstLine); 425 } 426 427 /** 428 * Get the first line for a given expression. 429 * 430 * @param aStartLine the line we are starting from 431 * @param aTree the expression to find the first line for 432 * 433 * @return the first line of the expression 434 */ 435 protected final int getFirstLine(int aStartLine, DetailAST aTree) 436 { 437 int realStart = aStartLine; 438 final int currLine = aTree.getLineNo(); 439 if (currLine < realStart) { 440 realStart = currLine; 441 } 442 443 // check children 444 for (DetailAST node = aTree.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 aAST 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 aAST) 463 { 464 final String line = 465 mIndentCheck.getLines()[aAST.getLineNo() - 1]; 466 467 return Utils.lengthExpandedTabs(line, aAST.getColumnNo(), 468 mIndentCheck.getIndentationTabWidth()); 469 } 470 471 /** 472 * Find the set of lines for a given subtree. 473 * 474 * @param aLines the set of lines to add to 475 * @param aTree the subtree to examine 476 * @param aAllowNesting whether or not to allow nested subtrees 477 */ 478 protected final void findSubtreeLines(LineSet aLines, DetailAST aTree, 479 boolean aAllowNesting) 480 { 481 if (getIndentCheck().getHandlerFactory().isHandledType(aTree.getType()) 482 || (aTree.getLineNo() < 0)) 483 { 484 return; 485 } 486 487 final int lineNum = aTree.getLineNo(); 488 final Integer colNum = aLines.getStartColumn(lineNum); 489 490 final int thisLineColumn = expandedTabsColumnNo(aTree); 491 if ((colNum == null) || (thisLineColumn < colNum.intValue())) { 492 aLines.addLineAndCol(lineNum, thisLineColumn); 493 } 494 495 // check children 496 for (DetailAST node = aTree.getFirstChild(); 497 node != null; 498 node = node.getNextSibling()) 499 { 500 findSubtreeLines(aLines, node, aAllowNesting); 501 } 502 } 503 504 /** 505 * Check the indentation level of modifiers. 506 */ 507 protected void checkModifiers() 508 { 509 final DetailAST modifiers = 510 mMainAst.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 mIndentCheck; 537 } 538 539 /** 540 * Accessor for the MainAst attribute. 541 * 542 * @return the MainAst attribute 543 */ 544 protected final DetailAST getMainAst() 545 { 546 return mMainAst; 547 } 548 549 /** 550 * Accessor for the Parent attribute. 551 * 552 * @return the Parent attribute 553 */ 554 protected final ExpressionHandler getParent() 555 { 556 return mParent; 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 aRparen parenthesis to check 581 * @param aLparen left parenthesis associated with aRparen 582 */ 583 protected final void checkRParen(DetailAST aLparen, DetailAST aRparen) 584 { 585 // no paren - no check :) 586 if (aRparen == 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(aRparen); 593 if (getLevel().accept(rparenLevel) || !startsLine(aRparen)) { 594 return; 595 } 596 597 // or has <lparen level> + 1 indentation 598 final int lparenLevel = expandedTabsColumnNo(aLparen); 599 if (rparenLevel == (lparenLevel + 1)) { 600 return; 601 } 602 603 logError(aRparen, "rparen", rparenLevel); 604 } 605 606 /** 607 * Check the indentation of the left parenthesis. 608 * @param aLparen parenthesis to check 609 */ 610 protected final void checkLParen(final DetailAST aLparen) 611 { 612 // the rcurly can either be at the correct indentation, or on the 613 // same line as the lcurly 614 if ((aLparen == null) 615 || getLevel().accept(expandedTabsColumnNo(aLparen)) 616 || !startsLine(aLparen)) 617 { 618 return; 619 } 620 logError(aLparen, "lparen", expandedTabsColumnNo(aLparen)); 621 } 622}