001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 */ 029public class MethodCallHandler extends AbstractExpressionHandler { 030 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 getIndentImpl() { 046 final 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 || areMethodsChained(container.getMainAst(), getMainAst())) { 055 indentLevel = container.getIndent(); 056 } 057 // we should increase indentation only if this is the first 058 // chained method call which was moved to the next line 059 else { 060 indentLevel = new IndentLevel(container.getIndent(), getBasicOffset()); 061 } 062 } 063 else if (getMainAst().getFirstChild().getType() == TokenTypes.LITERAL_NEW) { 064 indentLevel = super.getIndentImpl(); 065 } 066 else { 067 // if our expression isn't first on the line, just use the start 068 // of the line 069 final LineSet lines = new LineSet(); 070 findSubtreeLines(lines, getMainAst().getFirstChild(), true); 071 final int firstCol = lines.firstLineCol(); 072 final int lineStart = getLineStart(getFirstAst(getMainAst())); 073 if (lineStart == firstCol) { 074 indentLevel = super.getIndentImpl(); 075 } 076 else { 077 indentLevel = new IndentLevel(lineStart); 078 } 079 } 080 return indentLevel; 081 } 082 083 /** 084 * Checks if ast2 is a chained method call that starts on the same level as ast1 ends. 085 * In other words, if the right paren of ast1 is on the same level as the lparen of ast2: 086 * 087 * {@code 088 * value.methodOne( 089 * argument1 090 * ).methodTwo( 091 * argument2 092 * ); 093 * } 094 * 095 * @param ast1 Ast1 096 * @param ast2 Ast2 097 * @return True if ast2 begins on the same level that ast1 ends 098 */ 099 private static boolean areMethodsChained(DetailAST ast1, DetailAST ast2) { 100 final DetailAST rparen = ast1.findFirstToken(TokenTypes.RPAREN); 101 return rparen.getLineNo() == ast2.getLineNo(); 102 } 103 104 /** 105 * If this is the first chained method call which was moved to the next line. 106 * @return true if chained class are wrapped 107 */ 108 private boolean isChainedMethodCallWrapped() { 109 boolean result = false; 110 final DetailAST main = getMainAst(); 111 final DetailAST dot = main.getFirstChild(); 112 final DetailAST target = dot.getFirstChild(); 113 114 final DetailAST dot1 = target.getFirstChild(); 115 final DetailAST target1 = dot1.getFirstChild(); 116 117 if (dot1.getType() == TokenTypes.DOT 118 && target1.getType() == TokenTypes.METHOD_CALL) { 119 result = true; 120 } 121 return result; 122 } 123 124 /** 125 * Get the first AST of the specified method call. 126 * 127 * @param ast 128 * the method call 129 * 130 * @return the first AST of the specified method call 131 */ 132 private static DetailAST getFirstAst(DetailAST ast) { 133 // walk down the first child part of the dots that make up a method 134 // call name 135 136 DetailAST astNode = ast.getFirstChild(); 137 while (astNode.getType() == TokenTypes.DOT) { 138 astNode = astNode.getFirstChild(); 139 } 140 return astNode; 141 } 142 143 /** 144 * Returns method or constructor name. For {@code foo(arg)} it is `foo`, for 145 * {@code foo.bar(arg)} it is `bar` for {@code super(arg)} it is 'super'. 146 * 147 * @return TokenTypes.IDENT node for a method call, TokenTypes.SUPER_CTOR_CALL otherwise. 148 */ 149 private DetailAST getMethodIdentAst() { 150 DetailAST ast = getMainAst(); 151 if (ast.getType() != TokenTypes.SUPER_CTOR_CALL) { 152 ast = ast.getFirstChild(); 153 if (ast.getType() == TokenTypes.DOT) { 154 ast = ast.getLastChild(); 155 } 156 } 157 return ast; 158 } 159 160 @Override 161 public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) { 162 // for whatever reason a method that crosses lines, like asList 163 // here: 164 // System.out.println("methods are: " + Arrays.asList( 165 // new String[] {"method"}).toString()); 166 // will not have the right line num, so just get the child name 167 168 final DetailAST ident = getMethodIdentAst(); 169 IndentLevel suggestedLevel = new IndentLevel(getLineStart(ident)); 170 if (!areOnSameLine(child.getMainAst().getFirstChild(), ident)) { 171 suggestedLevel = new IndentLevel(suggestedLevel, 172 getBasicOffset(), 173 getIndentCheck().getLineWrappingIndentation()); 174 } 175 176 // If the right parenthesis is at the start of a line; 177 // include line wrapping in suggested indent level. 178 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN); 179 if (getLineStart(rparen) == rparen.getColumnNo()) { 180 suggestedLevel.addAcceptedIndent(new IndentLevel( 181 getParent().getSuggestedChildIndent(this), 182 getIndentCheck().getLineWrappingIndentation() 183 )); 184 } 185 186 return suggestedLevel; 187 } 188 189 @Override 190 public void checkIndentation() { 191 DetailAST lparen = null; 192 if (getMainAst().getType() == TokenTypes.METHOD_CALL) { 193 final DetailAST exprNode = getMainAst().getParent(); 194 if (exprNode.getParent().getType() == TokenTypes.SLIST) { 195 checkExpressionSubtree(getMainAst().getFirstChild(), getIndent(), false, false); 196 lparen = getMainAst(); 197 } 198 } 199 else { 200 // TokenTypes.CTOR_CALL|TokenTypes.SUPER_CTOR_CALL 201 lparen = getMainAst().getFirstChild(); 202 } 203 204 if (lparen != null) { 205 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN); 206 checkLeftParen(lparen); 207 208 if (rparen.getLineNo() != lparen.getLineNo()) { 209 checkExpressionSubtree( 210 getMainAst().findFirstToken(TokenTypes.ELIST), 211 new IndentLevel(getIndent(), getBasicOffset()), 212 false, true); 213 214 checkRightParen(lparen, rparen); 215 checkWrappingIndentation(getMainAst(), getCallLastNode(getMainAst())); 216 } 217 } 218 } 219 220 @Override 221 protected boolean shouldIncreaseIndent() { 222 return false; 223 } 224 225 /** 226 * Returns method or constructor call right paren. 227 * @param firstNode 228 * call ast(TokenTypes.METHOD_CALL|TokenTypes.CTOR_CALL|TokenTypes.SUPER_CTOR_CALL) 229 * @return ast node containing right paren for specified method or constructor call. If 230 * method calls are chained returns right paren for last call. 231 */ 232 private static DetailAST getCallLastNode(DetailAST firstNode) { 233 return firstNode.getLastChild(); 234 } 235 236}