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 com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.TokenTypes; 024 025/** 026 * Handler for method calls. 027 * 028 * @author jrichard 029 */ 030public class MethodCallHandler extends AbstractExpressionHandler { 031 /** 032 * Construct an instance of this handler with the given indentation check, 033 * abstract syntax tree, and parent handler. 034 * 035 * @param indentCheck the indentation check 036 * @param ast the abstract syntax tree 037 * @param parent the parent handler 038 */ 039 public MethodCallHandler(IndentationCheck indentCheck, 040 DetailAST ast, AbstractExpressionHandler parent) { 041 super(indentCheck, "method call", ast, parent); 042 } 043 044 @Override 045 protected IndentLevel getLevelImpl() { 046 IndentLevel indentLevel; 047 // if inside a method call's params, this could be part of 048 // an expression, so get the previous line's start 049 if (getParent() instanceof MethodCallHandler) { 050 final MethodCallHandler container = 051 (MethodCallHandler) getParent(); 052 if (areOnSameLine(container.getMainAst(), getMainAst()) 053 || isChainedMethodCallWrapped()) { 054 indentLevel = container.getLevel(); 055 } 056 // we should increase indentation only if this is the first 057 // chained method call which was moved to the next line 058 else { 059 indentLevel = new IndentLevel(container.getLevel(), getBasicOffset()); 060 } 061 } 062 else { 063 // if our expression isn't first on the line, just use the start 064 // of the line 065 final LineSet lines = new LineSet(); 066 findSubtreeLines(lines, getMainAst().getFirstChild(), true); 067 final int firstCol = lines.firstLineCol(); 068 final int lineStart = getLineStart(getFirstAst(getMainAst())); 069 if (lineStart == firstCol) { 070 indentLevel = super.getLevelImpl(); 071 } 072 else { 073 indentLevel = new IndentLevel(lineStart); 074 } 075 } 076 return indentLevel; 077 } 078 079 /** 080 * If this is the first chained method call which was moved to the next line. 081 * @return true if chained class are wrapped 082 */ 083 private boolean isChainedMethodCallWrapped() { 084 boolean result = false; 085 final DetailAST main = getMainAst(); 086 final DetailAST dot = main.getFirstChild(); 087 final DetailAST target = dot.getFirstChild(); 088 089 final DetailAST dot1 = target.getFirstChild(); 090 final DetailAST target1 = dot1.getFirstChild(); 091 092 if (dot1.getType() == TokenTypes.DOT 093 && target1.getType() == TokenTypes.METHOD_CALL) { 094 result = true; 095 } 096 return result; 097 } 098 099 /** 100 * Get the first AST of the specified method call. 101 * 102 * @param ast 103 * the method call 104 * 105 * @return the first AST of the specified method call 106 */ 107 private static DetailAST getFirstAst(DetailAST ast) { 108 // walk down the first child part of the dots that make up a method 109 // call name 110 111 DetailAST astNode = ast.getFirstChild(); 112 while (astNode.getType() == TokenTypes.DOT) { 113 astNode = astNode.getFirstChild(); 114 } 115 return astNode; 116 } 117 118 @Override 119 public IndentLevel suggestedChildLevel(AbstractExpressionHandler child) { 120 // for whatever reason a method that crosses lines, like asList 121 // here: 122 // System.out.println("methods are: " + Arrays.asList( 123 // new String[] {"method"}).toString()); 124 // will not have the right line num, so just get the child name 125 126 final DetailAST first = getMainAst().getFirstChild(); 127 IndentLevel suggestedLevel = new IndentLevel(getLineStart(first)); 128 if (!areOnSameLine(child.getMainAst().getFirstChild(), 129 getMainAst().getFirstChild())) { 130 suggestedLevel = new IndentLevel(suggestedLevel, 131 getBasicOffset(), 132 getIndentCheck().getLineWrappingIndentation()); 133 } 134 135 // If the right parenthesis is at the start of a line; 136 // include line wrapping in suggested indent level. 137 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN); 138 if (getLineStart(rparen) == rparen.getColumnNo()) { 139 suggestedLevel.addAcceptedIndent(new IndentLevel( 140 getParent().suggestedChildLevel(this), 141 getIndentCheck().getLineWrappingIndentation() 142 )); 143 } 144 145 return suggestedLevel; 146 } 147 148 @Override 149 public void checkIndentation() { 150 final DetailAST exprNode = getMainAst().getParent(); 151 if (exprNode.getParent().getType() != TokenTypes.SLIST) { 152 return; 153 } 154 final DetailAST methodName = getMainAst().getFirstChild(); 155 checkExpressionSubtree(methodName, getLevel(), false, false); 156 157 final DetailAST lparen = getMainAst(); 158 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN); 159 checkLParen(lparen); 160 161 if (rparen.getLineNo() == lparen.getLineNo()) { 162 return; 163 } 164 165 checkExpressionSubtree( 166 getMainAst().findFirstToken(TokenTypes.ELIST), 167 new IndentLevel(getLevel(), getBasicOffset()), 168 false, true); 169 170 checkRParen(lparen, rparen); 171 final LineWrappingHandler lineWrap = 172 new LineWrappingHandler(getIndentCheck(), getMainAst(), 173 getMethodCallLastNode(getMainAst())); 174 lineWrap.checkIndentation(); 175 } 176 177 @Override 178 protected boolean shouldIncreaseIndent() { 179 return false; 180 } 181 182 /** 183 * Returns method call right paren. 184 * @param firstNode 185 * method call ast(TokenTypes.METHOD_CALL) 186 * @return ast node containing right paren for specified method call. If 187 * method calls are chained returns right paren for last call. 188 */ 189 private static DetailAST getMethodCallLastNode(DetailAST firstNode) { 190 return firstNode.getLastChild(); 191 } 192}